Advanced Graphics and Data Visualization in R

Lecture 05: Dimension Transformation LIVE HTML Test

0.1.0 An overview of Advanced Graphics and Data Visualization in R

“Advanced Graphics and Data Visualization in R” is brought to you by the Centre for the Analysis of Genome Evolution & Function’s (CAGEF) bioinformatics training initiative. This CSB1021 was developed to enhance the skills of students with basic backgrounds in R by focusing on available philosophies, methods, and packages for plotting scientific data. While the datasets and examples used in this course will be centred on SARS-CoV-2 epidemiological and genomic data, the lessons learned herein will be broadly applicable.

This lesson is the fifth in a 6-part series. The aim for the end of this series is for students to recognize how to import, format, and display data based on their intended message and audience. The format and style of these visualizations will help to identify and convey the key message(s) from their experimental data.

The structure of the class is a code-along style in R markdown notebooks. At the start of each lecture, skeleton versions of the lecture will be provided for use on the University of Toronto datatools Hub so students can program along with the instructor.


0.2.0 Lecture objectives

This week will focus on exploring the relationships within your data observations comparing between experimental samples and applying a suite of cluster, dimensions reduction and projection algorithms.

At the end of this lecture you will have covered visualizations related to

  1. Clustering with heatmaps and dendrograms
  2. K-means clustering
  3. Principal component analysis
  4. Non-linear projections

0.3.0 A legend for text format in R markdown

grey background - a package, function, code, command or directory. Backticks are also use for in-line code.
italics - an important term or concept or an individual file or folder
bold - heading or a term that is being defined
blue text - named or unnamed hyperlink

... - Within each coding cell this will indicate an area of code that students will need to complete for the code cell to run correctly.

Blue box: A key concept that is being introduced

Yellow box: Risk or caution

Green boxes: Recommended reads and resources to learn R

Red boxes: A comprehension question which may or may not involve a coding cell. You usually find these at the end of a section.


0.4.0 Lecture and data files used in this course

0.4.1 Weekly Lecture and skeleton files

Each week, new lesson files will appear within your RStudio folders. We are pulling from a GitHub repository using this Repository git-pull link. Simply click on the link and it will take you to the University of Toronto datatools Hub. You will need to use your UTORid credentials to complete the login process. From there you will find each week’s lecture files in the directory /2024-03-Adv_Graphics_R/Lecture_XX. You will find a partially coded skeleton.Rmd file as well as all of the data files necessary to run the week’s lecture.

Alternatively, you can download the R-Markdown Notebook (.Rmd) and data files from the RStudio server to your personal computer if you would like to run independently of the Toronto tools.

0.4.2 Live-coding HTML page

A live lecture version will be available at camok.github.io that will update as the lecture progresses. Be sure to refresh to take a look if you get lost!

0.4.3 Post-lecture PDFs

As mentioned above, at the end of each lecture there will be a completed version of the lecture code released as a PDF file under the Modules section of Quercus.

0.4.4 Data used in this lesson

Today’s datasets will focus on the smaller datasets we worked on in earlier lectures (namely our Ontario public health unit COVID-19 demographics data), and a new set of RNAseq analysis on different tissue samples from COVID-19 patients

0.4.4.1 Dataset 1: phu_demographics_census_norm.csv

This is a combination of datasets from previous lectures. This incorporates PHU demographic data with StatsCan census data from 2017 to produce a normalized estimate of cases, deaths, and hospitalizations across age groups and public health units in Ontario.

0.4.4.2 Dataset 2: Wyler2020_AEC_SARSCoV2_17AAG_readcounts.tsv

This is the same readcount data we looked at in Lecture 04. RNA-Seq read count data generated from SARS-CoV2 infections of AEC cells. Used to compare the timecourse of expression (pro-inflammatory) changes in samples treated with and without HSP90 inhibitors. Published in iScience doi: https://doi.org/10.1016/j.isci.2021.102151

0.4.4.3 Dataset 3: GSE150316_DeseqNormCounts_final.txt

From Desai et al., 2020 on medRxiv doi: https://doi.org/10.1101/2020.07.30.20165241 this dataset has normalized expression counts from RNAseq data. It covers multiple samples and tissues from COVID-positive patients with a focus on lung tissue. The expression data has been unfiltered for SARS-CoV-2 expression data as well.

0.4.4.4 Dataset 4: 2020.07.30.20165241-supp_tables.xlsx

From Desai et al., 2020 on medRxiv doi: https://doi.org/10.1101/2020.07.30.20165241 this dataset contains patient information like viral load from tissues that were used for RNAseq.


0.5.0 Packages used in this lesson

tidyverse which has a number of packages including dplyr, tidyr, stringr, forcats and ggplot2

viridis helps to create color-blind palettes for our data visualizations

RColorBrewer has some hlepful palettes that we’ll need to colour our data.

gplots will be used to help generate heatmap/dendrogram visualizations.

FactoMineR and factoextra will be used for PCA generation and visualization

Rtsne and umap are packages implementing non-linear projection algorithms

# Some packages can be installed via Bioconductor
if (!requireNamespace("BiocManager", quietly = TRUE))
    install.packages("BiocManager")

BiocManager::install("ComplexHeatmap")
'getOption("repos")' replaces Bioconductor standard repositories, see
'help("repositories", package = "BiocManager")' for details.
Replacement repositories:
    CRAN: https://cran.r-project.org
Bioconductor version 3.12 (BiocManager 1.30.22), R 4.0.5 (2021-03-31)
Installing package(s) 'ComplexHeatmap'
also installing the dependencies 'shape', 'rjson', 'circlize', 'GetoptLong', 'clue', 'GlobalOptions'

  There are binary versions available but the source versions are later:
         binary  source needs_compilation
shape     1.4.6 1.4.6.1             FALSE
circlize 0.4.14  0.4.16             FALSE
clue     0.3-60  0.3-65              TRUE

package 'rjson' successfully unpacked and MD5 sums checked
package 'GetoptLong' successfully unpacked and MD5 sums checked
package 'GlobalOptions' successfully unpacked and MD5 sums checked
package 'ComplexHeatmap' successfully unpacked and MD5 sums checked

The downloaded binary packages are in
    C:\Users\mokca\AppData\Local\Temp\RtmpaoMGiY\downloaded_packages
installing the source packages 'shape', 'circlize', 'clue'
Installation paths not writeable, unable to update packages
  path: C:/Program Files/R/R-4.0.5/library
  packages:
    boot, class, cluster, codetools, digest, evaluate, fastmap, foreign, glue,
    KernSmooth, lattice, MASS, mgcv, nlme, nnet, pbdZMQ, rpart, spatial,
    survival
Old packages: 'bdsmatrix', 'bit', 'brew', 'brio', 'bslib', 'cachem', 'Cairo',
  'callr', 'cli', 'colorspace', 'commonmark', 'coneproj', 'covr', 'cowplot',
  'coxme', 'cpp11', 'crosstalk', 'curl', 'data.table', 'DBI', 'dbplyr', 'desc',
  'dplyr', 'dreamerr', 'DT', 'fansi', 'farver', 'fixest', 'foghorn',
  'fontawesome', 'future', 'gee', 'gert', 'ggrepel', 'ggsci', 'gh', 'git2r',
  'glmmTMB', 'globals', 'haven', 'htmltools', 'htmlwidgets', 'httpuv', 'httr2',
  'hunspell', 'isoband', 'jsonlite', 'knitr', 'labeling', 'later', 'lifecycle',
  'lintr', 'listenv', 'lme4', 'lubridate', 'markdown', 'Matrix',
  'MatrixModels', 'matrixStats', 'mlt', 'mockery', 'munsell', 'mvtnorm',
  'odbc', 'openssl', 'parallelly', 'pingr', 'pkgbuild', 'pkgdown', 'pkgload',
  'plyr', 'prettyunits', 'processx', 'progress', 'promises', 'ragg', 'Rcpp',
  'RcppArmadillo', 'RcppEigen', 'RCurl', 'readr', 'remotes', 'renv', 'repr',
  'reprex', 'rlang', 'RMariaDB', 'rmarkdown', 'RMySQL', 'robustbase',
  'roxygen2', 'RPostgres', 'RPostgreSQL', 'rprojroot', 'rsconnect', 'RSQLite',
  'rstudioapi', 'rvest', 'rzmq', 'sandwich', 'sass', 'shiny', 'showtext',
  'spelling', 'stringi', 'stringr', 'sysfonts', 'systemfonts', 'textshaping',
  'tidyr', 'tidyselect', 'timechange', 'tinytex', 'TMB', 'tram', 'tzdb',
  'usethis', 'utf8', 'uuid', 'vctrs', 'vipor', 'viridis', 'vroom', 'waldo',
  'webutils', 'withr', 'writexl', 'xfun', 'xml2', 'xts', 'yaml', 'zip'
install.packages("FactoMineR", dependencies = TRUE)
Installing package into 'C:/Users/mokca/AppData/Local/R/win-library/4.0'
(as 'lib' is unspecified)
Warning: dependency 'emmeans' is not available
also installing the dependencies 'ucminf', 'ordinal', 'pan', 'jomo', 'glmnet', 'mitml', 'iterators', 'mice', 'doParallel', 'foreach', 'FactoInvestigate', 'shinydashboard', 'shinyjqui', 'ellipse', 'flashClust', 'leaps', 'multcompView', 'scatterplot3d', 'missMDA', 'Factoshiny'

  There are binary versions available but the source versions are later:
                     binary    source needs_compilation
ucminf                1.1-4     1.2.1              TRUE
ordinal          2019.12-10 2023.12-4              TRUE
pan                     1.6       1.9              TRUE
jomo                  2.7-3     2.7-6              TRUE
glmnet                4.1-4     4.1-8              TRUE
mitml                 0.4-3     0.4-5             FALSE
mice                 3.14.0    3.16.0              TRUE
FactoInvestigate        1.7       1.9             FALSE
ellipse               0.4.2     0.5.0             FALSE
multcompView          0.1-8    0.1-10             FALSE
scatterplot3d        0.3-41    0.3-44             FALSE
missMDA                1.18      1.19             FALSE
Factoshiny              2.4       2.5             FALSE
FactoMineR              2.4      2.10              TRUE

package 'iterators' successfully unpacked and MD5 sums checked
package 'doParallel' successfully unpacked and MD5 sums checked
package 'foreach' successfully unpacked and MD5 sums checked
package 'shinydashboard' successfully unpacked and MD5 sums checked
package 'shinyjqui' successfully unpacked and MD5 sums checked
package 'flashClust' successfully unpacked and MD5 sums checked
package 'leaps' successfully unpacked and MD5 sums checked

The downloaded binary packages are in
    C:\Users\mokca\AppData\Local\Temp\RtmpaoMGiY\downloaded_packages
installing the source packages 'ucminf', 'ordinal', 'pan', 'jomo', 'glmnet', 'mitml', 'mice', 'FactoInvestigate', 'ellipse', 'multcompView', 'scatterplot3d', 'missMDA', 'Factoshiny', 'FactoMineR'
Warning in install.packages("FactoMineR", dependencies = TRUE): installation of
package 'FactoMineR' had non-zero exit status
Warning in install.packages("FactoMineR", dependencies = TRUE): installation of
package 'FactoInvestigate' had non-zero exit status
Warning in install.packages("FactoMineR", dependencies = TRUE): installation of
package 'missMDA' had non-zero exit status
Warning in install.packages("FactoMineR", dependencies = TRUE): installation of
package 'Factoshiny' had non-zero exit status
install.packages("factoextra", dependencies = TRUE)
Installing package into 'C:/Users/mokca/AppData/Local/R/win-library/4.0'
(as 'lib' is unspecified)
Warning: dependency 'emmeans' is not available
also installing the dependencies 'pixmap', 'cpp11', 'dendextend', 'FactoMineR', 'ade4', 'ca', 'igraph', 'mclust'

  There are binary versions available but the source versions are later:
           binary source needs_compilation
cpp11       0.4.2  0.4.7             FALSE
dendextend 1.15.2 1.17.1             FALSE
FactoMineR    2.4   2.10              TRUE
ade4       1.7-19 1.7-22              TRUE
igraph      1.3.0  2.0.3              TRUE
mclust      5.4.9    6.1              TRUE

package 'pixmap' successfully unpacked and MD5 sums checked
package 'ca' successfully unpacked and MD5 sums checked
package 'factoextra' successfully unpacked and MD5 sums checked

The downloaded binary packages are in
    C:\Users\mokca\AppData\Local\Temp\RtmpaoMGiY\downloaded_packages
installing the source packages 'cpp11', 'dendextend', 'FactoMineR', 'ade4', 'igraph', 'mclust'
Warning in install.packages("factoextra", dependencies = TRUE): installation of
package 'FactoMineR' had non-zero exit status
Warning in install.packages("factoextra", dependencies = TRUE): installation of
package 'igraph' had non-zero exit status
install.packages("Rtsne")
Installing package into 'C:/Users/mokca/AppData/Local/R/win-library/4.0'
(as 'lib' is unspecified)

  There is a binary version available but the source version is later:
      binary source needs_compilation
Rtsne   0.16   0.17              TRUE
installing the source package 'Rtsne'
install.packages("umap")
Installing package into 'C:/Users/mokca/AppData/Local/R/win-library/4.0'
(as 'lib' is unspecified)
also installing the dependencies 'RcppTOML', 'here', 'reticulate', 'RSpectra'

  There are binary versions available but the source versions are later:
            binary   source needs_compilation
RcppTOML     0.1.7    0.2.2              TRUE
reticulate    1.24   1.35.0              TRUE
RSpectra    0.16-0   0.16-1              TRUE
umap       0.2.8.0 0.2.10.0              TRUE

package 'here' successfully unpacked and MD5 sums checked

The downloaded binary packages are in
    C:\Users\mokca\AppData\Local\Temp\RtmpaoMGiY\downloaded_packages
installing the source packages 'RcppTOML', 'reticulate', 'RSpectra', 'umap'
# ------------ Legacy installation code which we hopefully never need again ----------#
# We need to specifically install a package called pbkrtest for the facto packages
# This is due to using an older verion of R 
# library(remotes)
# install_version("pbkrtest", "0.5.1")
# Packages to help tidy our data
library(tidyverse)
-- Attaching core tidyverse packages ---------------------------------------------------------------- tidyverse 2.0.0 --
v dplyr     1.1.0     v readr     2.1.4
v forcats   1.0.0     v stringr   1.5.0
v ggplot2   3.5.0     v tibble    3.2.1
v lubridate 1.9.2     v tidyr     1.3.0
v purrr     1.0.2     
-- Conflicts ---------------------------------------------------------------------------------- tidyverse_conflicts() --
x dplyr::filter()   masks stats::filter()
x purrr::is_empty() masks git2r::is_empty()
x dplyr::lag()      masks stats::lag()
x dplyr::pull()     masks git2r::pull()
x purrr::when()     masks git2r::when()
i Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(readxl)
library(magrittr)

Attaching package: 'magrittr'

The following object is masked from 'package:purrr':

    set_names

The following object is masked from 'package:tidyr':

    extract

The following object is masked from 'package:git2r':

    add
# Packages for the graphical analysis section
library(viridis)
Loading required package: viridisLite
# library(gplots) # heatmap2()
library(ComplexHeatmap) # Heatmap()
Loading required package: grid
========================================
ComplexHeatmap version 2.6.2
Bioconductor page: http://bioconductor.org/packages/ComplexHeatmap/
Github page: https://github.com/jokergoo/ComplexHeatmap
Documentation: http://jokergoo.github.io/ComplexHeatmap-reference

If you use it in published research, please cite:
Gu, Z. Complex heatmaps reveal patterns and correlations in multidimensional 
  genomic data. Bioinformatics 2016.

This message can be suppressed by:
  suppressPackageStartupMessages(library(ComplexHeatmap))
========================================
library(RColorBrewer)

# Useful for PCA and PCA visualization
library(FactoMineR)
Error in library(FactoMineR): there is no package called 'FactoMineR'
library(factoextra)
Welcome! Want to learn more? See two factoextra-related books at https://goo.gl/ve3WBa
# Data projection packages
library(Rtsne)
library(umap)

1.0.0 Data categorization, dimension reduction, and dimension transformation

Last week we looked at an analysis of RNAseq data through a number of methods starting with broad-level volcano plots and moving towards gene-level expression visualizations with dotplots. In between we stopped to take a look at heatmaps. In this instance we simply used heatmaps to convey expression levels of multiple genes across one or more samples.

Looking more broadly, we now wish to ask questions such as “how similar are our samples?”, “can our samples be grouped or categorized in some way?” and “is there an underlying structure or architecture to our data?” We briefly discussed scatterplot matrices to help analyse our sample quality amongst replicate experiments.

We can study these questions using a number of techniques that range from clustering/categorization to projection of high-dimensional data onto a lower set of dimensions (usually 2 or 3).


1.1.0 What kind of data do we care about?

We cannot begin our journey until we talk about the nature of the data we are interested in examining. Usually, our data will consist of many samples (observations) with some number of features captured about each observation. For example, with RNAseq data we could consider the measurements we capture for each gene as a separate feature/variable/column.

Conversely you may have hundreds or thousands of samples you’d like to categorize in some way (or show that you can categorize) with just a smaller set of features. For every nut, there is a tool of sorts for cracking it!

We’ll start with a modified version of the PHU age group dataset that we’ve seen before from Lecture 02 and 03. The demographics data has been modified to include a set of normalized values (individuals per 100k) that was calculated based on StatsCan 2017 census data. Modeling off of these data, the 2022 sizes for each age group were estimated. Case, death, and hospitalization counts were normalized based on these subgroup sizes to generate values per 100K individuals. We’ll be working with this 2022 dataset mainly because it utilizes more fine-grained binning of our age-groups than more recent dataset from the Ontario government archives.

The updated dataset will be found in phu_demographics_census_norm.csv and we’ll use it to guide us through two sections of today’s lecture.

Let’s begin by loading the data and taking a look at its structure.

# Import the normalized demographics data
covid_demographics_norm.df = read_csv(...)
Error in eval(expr, envir, enclos): '...' used in an incorrect context
# Look at it's structure
str(covid_demographics_norm.df, give.attr = FALSE)
Error in str(covid_demographics_norm.df, give.attr = FALSE): object 'covid_demographics_norm.df' not found

1.2.0 Looking for trends/groups in your data

Let’s begin looking at our covid_demographics_norm.df. Using a series of data sets, we’ve created a consolidated dataset with:

  1. Cumulative cases, deaths, and hospitalizations due to COVID-19 within each age group per PHU.

  2. Representation of each age group within each data category as a percent of total incidents in each PHU.

  3. Using 2017 census data, the number of cases per 100,000 individuals normalized by estimated population size for each age group within each PHU.

The question we want to answer is: of the 34 public health units, which look most similar based on the normalized case data for each age group? In order to visualize this data, we’ll want to convert our current long-form data that looks something like this:

public_health_unit age_group total_cases cases_per_100k deaths_per_100k hospitalizations_per_100k
Algoma 0 to 4 181 3302 0 164
Algoma 12 to 19 412 4464 0 54

Into something like this:

public_health_unit population_2022 category 0 to 4 5 to 11 12 to 19 20 to 39 40 to 59 60 to 79 80+
Algoma 117840 cases 3302 4464 7570 4301 4890 2331 4116

We need to do the following to the dataset

  1. Select just the variables we are interested in.
  2. pivot out the age group data into its own columns so we have 1 observation (row) per PHU.
  3. Correct the position of the age groups (5 to 11 needs to be moved).
# Create a wide-format version of our normalized data
covid_demographics_norm_wide.df <-

  # Start by passing along the long-form normalized data
  covid_demographics_norm.df %>% 

  # Select for just the PHU, age_group, population size and cases/hosp/deaths_per_100k
  dplyr::select(c(3,4,11,13:15)) %>% 

  # Pivot the data to a longer format
  pivot_longer(cols = -c(1:3), names_to = ..., values_to = ...) %>% 

  # Get rid of the suffix of "_per_100k"
  mutate(category = ...(string = category, pattern = "_per_100k")) %>% 

  # Pivot the age_group/per_100k data out wider
  pivot_wider(names_from = ...,
              values_from = ...
             ) %>% 

  # Move the "5 to 11" category to after "0 to 4". You could use a longer "select" call to do this too!
  relocate(., `5 to 11`, .after=`0 to 4`)
Error in relocate(., `5 to 11`, .after = `0 to 4`): '...' used in an incorrect context
# Take a look at our resulting dataframe
head(covid_demographics_norm_wide.df)
Error in head(covid_demographics_norm_wide.df): object 'covid_demographics_norm_wide.df' not found

1.2.1 Cast our data to a matrix for heatmaps

At this point, we would normally be prepared to visualize our data with ggplot but our data is in wide-format and we’ll be using a package that prefers to work with matrix data. In that case, we need to strip the data down further because matrix data must be all of the same type and we want to work with numeric data! We’ll use as.matrix() for our conversion.

  1. We’ll filter our dataset to only include the “cases” data we saw above, and then drop the category variable from it.
  2. We’ll move the public_health_unit information over to the row names of our matrix so we can still track our data properly.
  3. We’ll still keep our population_2022 column of data but we’ll have to remember that it’s there.
# Now we need to make our matrix and assign row names. 
# It's kind of awkward to need this requirement.

# Cast our matrix and drop the first column
covid_demographics_norm.mx <- covid_demographics_norm_wide.df %>% 
  
  # Filter for just case data
  dplyr::filter(...) %>% 
  
  # Drop the PHU and category data
  dplyr::select(-c(public_health_unit, category)) %>%
  
  # Convert to a matrix
  as.matrix()
Error in dplyr::select(., -c(public_health_unit, category)): '...' used in an incorrect context
# Set the row names using the information from the data frame
rownames(covid_demographics_norm.mx) <- 
  covid_demographics_norm_wide.df %>% 
  
  # Pull the PHU names
  ... %>% 
  
  # Create a unique vector of them
  ...
Error in eval(expr, envir, enclos): '...' used in an incorrect context
# Take a peek at our data
head(covid_demographics_norm.mx)
Error in head(covid_demographics_norm.mx): object 'covid_demographics_norm.mx' not found

1.3.0 Plot and reorder our PHUs as a heatmap with Heatmap()

From the ComplexHeatmap package we can use the function Heatmap() to generate a heatmap that can do a little more than what we were doing with our ggplot2 version. A nice aspect of using Heatmap() is that we can ask it to reorder our samples along both the rows and columns. At the same time we can present the relationship between columns elements or row elements in the form of dendrograms.

1.3.1 How do we reorder our data via clustering?

In its current form, we have our ordered our data along the columns in an ascending ordinal arrangement. While this makes sense to us now, it may not help to visually identify the strongest trends or groupings in our data. Clustering attempts to bring order to our data by grouping data according to specific algorithms.

Overall single points are usually grouped together as neighbours with the “nearest” neighbours determined by a metric of some kind. These clusters are then further grouped using the same metrics until the entire set is presented in these ordered groups. These groupings/relationships can also be presented as dendrograms. In our current case, we aren’t concerned with determining groups per se but rather with just connecting them by their similarity and then creating a hierarchical order.

Our data is usually coded in n-dimensional space, depending on the nature of our dataset. For covid_demographics_norm.mx our 7 columns are coded by data from 34 PHUs meaning each column is coded in 34 dimensions or 34 features. Conversely, our 34 rows represent PHUs each coded by data across 7 age groups and therefore 7 dimensions.

Important or helpful parameters to run Heatmap():

  • matrix: our matrix object. It must be a matrix, and not a data frame. It could also be a vector but that becomes a single column.
  • col: determines the colours used for the image.
  • name: the name/title of the heatmap (also use as the legend title by default)
  • row_* : set our row text properties including titles and labels:
    • row_title, row_title_side, row_title_gp (graphic properties)
    • row_names_side row_names_gp
    • column properties can also be changed using column_*
  • cluster_rows and cluster_columns: logical parameters to determine if clustering should occur (default is TRUE)
  • show_heatmap_legend: whether or not to show the heatmap legend
  • heatmap_legend_param: Set the title of the legend specifically is list(title = "x")
  • show_[row/col]_dend: Whether or not to show dendograms for the row/column

There are actually a lot of options but these should help us make a basic one. Let’s plot our data with and without a dendrogram to compare.

?Heatmap
starting httpd help server ... done
# Create a Heatmap object

cases_hmap <- 
  # Supply our matrix minus the populations size
  Heatmap(...,                                   
                      
          cluster_rows = ..., cluster_columns = ..., # Don't cluster on either rows or columns

          # Use column_title as the title of our heatmap
          column_title = "Heatmap of COVID-19 cases in PHUs by age group: unclustered",
                      
          # Rotate the legend horizontally and give it a title
          heatmap_legend_param = list(title = "cases per 100K individuals",
                                      legend_direction = "horizontal"),
                      
          # Rotate column names to horizontal
          column_names_rot = 0,
          column_names_center = TRUE
         )
Error in eval(expr, envir, enclos): '...' used in an incorrect context
# Plot the heatmap 
...(cases_hmap, 
     # Plot the legend on the bottom
     heatmap_legend_side = "bottom"
    )
Error in ...(cases_hmap, heatmap_legend_side = "bottom"): could not find function "..."
# Create a Heatmap object
cases_hmap <- 
  # Supply our matrix minus the populations size
  Heatmap(covid_demographics_norm.mx[,-1],         
                      
          cluster_rows = ..., cluster_columns = ...,   # Cluster on both rows and columns
                      
          # Use column_title as the title of our heatmap
          column_title = "Heatmap of COVID-19 cases in PHUs by age group: clustered",
                      
          # Rotate the legend horizontally and give it a title
          heatmap_legend_param = list(title = "cases per 100K individuals",
                                      legend_direction = "horizontal"),
                      
          # Rotate column names to horizontal
          column_names_rot = 0,
          column_names_center = TRUE
         )
Error in eval(expr, envir, enclos): '...' used in an incorrect context
# Plot the heatmap 
draw(cases_hmap, 
     # Plot the legend on the bottom
     heatmap_legend_side = "bottom"
    )
Error in h(simpleError(msg, call)): error in evaluating the argument 'object' in selecting a method for function 'draw': object 'cases_hmap' not found

1.3.2 Concatenate multiple heatmaps together into a HeatmapList object

Our heatmap above is drawn from a single dataset category - cases - but it helps us to see that there are some strong signals that differentiate between our different PHUs. Now this is a 34x7 grid where we can investigate all of the data in our dataset. What happens when we want to produce this kind of data from our other two metrics of hospitalizations and deaths?

To accomplish this, we can repeat our steps and create 3 separate Heatmap objects but we can plot them together as a single complex heatmap. In fact, rather than store multiple heatmap objects, we’ll create a HeatmapList object by concatenating together multiple Heatmap objects using a for loop.

We’ll recreate our code from above but simplify the heatmap code a little. While we’re at it, we’ll also convert our colourscheme to viridis while we’re at it

# Create a list of heatmap objects from our demographic data
hm_list <- NULL

# Create some quick vectors to help ourselves out
categories <- ...
Error in eval(expr, envir, enclos): '...' used in an incorrect context
# Store the rownames we want to use on our matrices
mx_rownames <- covid_demographics_norm_wide.df %>% pull(public_health_unit) %>% unique()
Error in pull(., public_health_unit): object 'covid_demographics_norm_wide.df' not found
# Use a loop to separate the data by category
for (i in categories) {
  
  # Create a temporary matrix of the specific category data
  temp_mx <- covid_demographics_norm_wide.df %>% 
      dplyr::filter(category == i) %>% 
      dplyr::select(-c(public_health_unit, category)) %>% 
      as.matrix()
  
  # Set the rownames
  rownames(temp_mx) <- mx_rownames
  
  # Create a Heatmap object
  hmap <- Heatmap(temp_mx[,-1],   # Supply our matrix minus the populations size
                  
                  # Use a viridis colourscale broken into 100 segments
                  col = viridis(100),

                  # Use column_title as the title of our heatmap
                  column_title = i,
                  
                  # Rotate the legend horizontally and give it a title based on category
                  heatmap_legend_param = list(title = paste0(i, " per 100K individuals"),
                                              legend_direction = "horizontal")
                 )
  
  # Add this to our heatmap list
  hm_list <- ...
}
Error in eval(expr, envir, enclos): object 'categories' not found
# What kind of object is hm_list?
class(hm_list)
[1] "NULL"
NULL

1.3.3 draw() your HeatmapList

Now that we have our HeatmapList we can simply draw the whole thing and it will automatically concatenate for us. Of course there are many options we can use to display the list. One thing to note is that the clustering of columns in each heatmap is independent while the clustering of rows (and thus order) is based on only the first heatmap (by default).

Using the draw() method to display your HeatmapList will allow you to further customize details of the overall figure including setting row_title and column_title for the entire figure.

draw(..., 
     column_title = "COVID-19 metrics breakdown by age group and PHU\n",
     column_title_gp = gpar(fontsize = 20)
    )
Error in eval(expr, envir, enclos): '...' used in an incorrect context

1.4.0 Building correlation heatmaps with complex RNA-Seq data

Our heatmap above is drawn from a relatively simple dataset but it helps us to see that there are some strong signals that differentiate between our different PHUs. It become clear that while the bulk of cases in each PHU is dominated by the 20-39 age segment, the bulk of hopsitalizations and deaths originate from the 80+ segment.

Of course, this data was cumulative information from January 2020 to March 2022. With the aftermath of vaccinations and different variants, a look at the current demographics may reveals a shift in these trends.

Considering that the heatmap is a 34x7 grid, it is easier to visualize and dissect all of the data in our dataset. What happens, however, when we want to produce this kind of visualization from something much larger or more complex?

Recall from last lecture that we investigated read count data from Wyler et al., 2020 - Wyler2020_AEC_SARSCoV2_17AAG_readcounts.tsv. We used this dataset to produce a scatterplot matrix to compare between some of the replicate data within the set. Across this set there are 36 sets of RNA-Seq data spanning 27,011 genes.

Let’s begin by opening up the data file and getting a quick reminder of what it looks like.

# Read in your read_count data
wyler_readcounts.df <- read_tsv(...)
Error in eval(expr, envir, enclos): '...' used in an incorrect context
str(wyler_readcounts.df, give.attr = FALSE)
Error in str(wyler_readcounts.df, give.attr = FALSE): object 'wyler_readcounts.df' not found

1.4.1 Wrangle our readcount data

As you might recall from last week, there is quite a bit of data in this set. So that we can more easily visualize our data, we’ll want to wrangle it a bit more by:

  1. updating the column names to remove some of the unnecessary title information.
  2. filtering by readcounts to limit our genes to those with values between 1000 and 3000.
  3. Convert our dataframe to a matrix and rename the rows using the genes.

Why do we filter our read counts? In this instance we have chosen to filter our read counts. In some cases, you might find wild swings in expression, and it is what we’re looking for most of the time. For in-class analysis, to save a bit on memory and time, we try to limit the read counts to a narrow set to reduce our set size. It will also greatly reduce the size of our heatmap for viewing. By filtering, we’ll go from 27,011 observations to 109.

wyler_readcounts_filtered.df <-
    
    wyler_readcounts.df %>% 

    # Rename the columns be removing the first portion: AECII_xx
    rename_with(., ~ str_replace(string = .x, 
                                 pattern = r"(\w*_\d*_)", 
                                 replace = "")) %>% 

    # filter out the low-readcount data
    filter(if_all(.cols = -1, .fns = ~ .x > ... & .x < ...))
Error in rename_with(., ~str_replace(string = .x, pattern = "\\w*_\\d*_", : object 'wyler_readcounts.df' not found
# Create our data matrix
wyler_readcounts.mx <- as.matrix(wyler_readcounts_filtered.df[, ...])
Error in as.matrix(wyler_readcounts_filtered.df[, ...]): object 'wyler_readcounts_filtered.df' not found
# Set the row names using the information from the data frame
rownames(wyler_readcounts.mx) <- wyler_readcounts_filtered.df$gene
Error in eval(expr, envir, enclos): object 'wyler_readcounts_filtered.df' not found
# Check the characteristics of our matrix
head(wyler_readcounts.mx)
Error in head(wyler_readcounts.mx): object 'wyler_readcounts.mx' not found
str(wyler_readcounts.mx)
Error in str(wyler_readcounts.mx): object 'wyler_readcounts.mx' not found

1.4.2 Plot a heatmap of your readcount data

What if we were to produce a heatmap directly with the raw data? It would be impossible to read, and including a dendrogram would actually take forever to process given the 27,011 rows of data to process across the 36 datasets. That’s why we’ll use our small subset of data as an example to build a heatmap.

Let’s plot the data now with heatmap.2 and see how it looks with a dendrogram.

# Create a Heatmap object
wyler_hmap <- Heatmap(...,               # Supply our matrix 
                      cluster_rows = TRUE, cluster_columns = TRUE,   # Cluster on both rows and columns
                      col = ...,
                      
                      # Use column_title as the title of our heatmap
                      column_title = "Heatmap of Wyler et al., 2020 readcount data",
                      
                      # Rotate the legend horizontally and give it a title
                      heatmap_legend_param = list(title = "readcounts per gene",
                                                  legend_direction = "horizontal"),
                      
                     )
Error in eval(expr, envir, enclos): '...' used in an incorrect context
# Plot the heatmap 
draw(wyler_hmap, 
     # Plot the legend on the bottom
     heatmap_legend_side = "bottom"
    )
Error in h(simpleError(msg, call)): error in evaluating the argument 'object' in selecting a method for function 'draw': object 'wyler_hmap' not found

1.4.3 Generate a correlation matrix of your data

From the above output, we can see that our heatmap is already quite hard to read and that’s coming off of using just 109 genes! We do, however, get some strong trends regarding our datasets - we can see certain sets of replicates are grouped together in the data but not all of them. This might be clearer if we were to factor in all of the datapoints or just the relevant genes that define the differences between datasets. We’ll discuss the second part of that thought later in this lecture.

Recall that what we’re really interested in, regarding these readcount data, is to ask just how similar the experiments are between each other. Whereas we were a little limited by the space needed to produce the scatterplot matrix, we were able to produce correlation values between experiments on a small scale. We can extend that visualization forward to compare between all datasets and then visually summarize the data as a heatmap.

The portion of the scatterplot matrix we’ll extend is the correlation values. Whereas the ggpairs() function uses a Pearson correlation, we can choose between Pearson or Spearman correlation. Sometimes you may wish to compare both since Pearson examines linear relationships whereas Spearman uses a ranking method to compare variables for monotonic relationships.

To generate our matrix we’ll employ a couple of additional functions:

  • expand.grid(): we can use this to build a matrix of pair-wise combination values. We’ll use these to generate the pair-wise column combinations we want to compare with cor().

  • apply(): by now you should be familiar with this function. We’ll use it to iterate through our pair-wise column combinations and send each of those to…

  • cor(): this will return the correlation co-efficient based on the method we choose (pearson, kendall, spearman)

# Example of how you can use expand.grid to make pairwise combination between two sets of data.
expand.grid(c(...),c(...))
Error in expand.grid(c(...), c(...)): '...' used in an incorrect context
# First we need to fix the column names from our data
wyler_readcounts_only.df <-     
    
    # Use the FULL set of readcount data
    wyler_readcounts.df %>% 
    # Rename the columns be removing the first portion: AECII_xx
    rename_with(., ~ str_replace(string = .x, 
                                 pattern = r"(\w*_\d*_)", 
                                 replace = "")) %>% 
    # Drop the first two columns as well
    dplyr::select(c(3:38))
Error in rename_with(., ~str_replace(string = .x, pattern = "\\w*_\\d*_", : object 'wyler_readcounts.df' not found
# Create our correlation matrix
wyler_cor.mx <-
  # We'll apply a function to each row of the resulting grid pairs
  matrix(apply(expand.grid(c(...),c(...)),                     # generate the pair-wise combinations
               
               # explore it row-by-row
               MARGIN = 1,                                       
               
               # Apply a function to each row which generates a pearson correlation between
               # a specific pair of columns
               function(x) cor(wyler_readcounts_only.df[, x[1]], 
                               wyler_readcounts_only.df[, x[2]],
                               method = "pearson")               # Use a Pearson correlation
               ),  # End the apply function
                       
         nrow = 36,                                              # Cast our result as a matrix
         dimnames = list(colnames(wyler_readcounts_only.df),     # name the rows and columns
                         colnames(wyler_readcounts_only.df))
         )
Error in expand.grid(c(...), c(...)): '...' used in an incorrect context
# take a look at the final matrix
head(wyler_cor.mx)
Error in head(wyler_cor.mx): object 'wyler_cor.mx' not found

1.4.4 Generate a heatmap of your correlation matrix

Now that we’ve completed the correlation matrix and it appears to be correct, we can generate the heatmap of the data, allowing it to group data based on the Pearson correlation values.

# Create a Heatmap object
wyler_hmap <- 
  Heatmap(wyler_cor.mx,               # Supply our matrix 
                      
          # Cluster on both rows and columns
          cluster_rows = TRUE, cluster_columns = TRUE,   
          
          col = viridis(100),
                      
          # Use column_title as the title of our heatmap
          column_title = "Heatmap of RNA-Seq Pearson correlation on readcounts",
                      
          # Rotate the legend horizontally and give it a title
          heatmap_legend_param = list(title = "Pearson score",
                                      legend_direction = "horizontal"),

          # Set the row/column label font size
          row_names_gp = gpar(fontsize = 16),
          column_names_gp = gpar(fontsize = 16)
          )
Error in is.data.frame(matrix): object 'wyler_cor.mx' not found
# Plot the heatmap 
draw(wyler_hmap, 
     # Plot the legend on the bottom
     heatmap_legend_side = "bottom"
    )
Error in h(simpleError(msg, call)): error in evaluating the argument 'object' in selecting a method for function 'draw': object 'wyler_hmap' not found

1.5.0 Make a cluster dendrogram with hclust()

As you can see, clustering our data (on the right metric!) makes a big difference in how our data is displayed. In our last few heatmaps, we allowed Heatmap() to generate it’s own clustering. We could have supplied it with a specific function to calculate distances, or even a dendrogram object to order the data as well. Under the hood, the function is defaulting to euclidean distances and actually passing that information on to the hclust() function to produce the dendrograms.

The choice of method determines how the data is clustered. The choices include: ward.D, ward.D2, single, complete (default), average, mcquitty, median and centroid.

In general ward.D, ward.D2 and complete strategies try to find compact small “spherical” clusters. The single linkage adopts a ‘friends of friends’ clustering strategy. The other methods can be said to aim for somewhere in between. For more information on these, you can dig into the hclust() documentation

We can turn to hclust() to generate just dendrograms for us but prior to that we still need to reformat our matrix a little using the dist() function.


1.5.1 Calculate the distance between our points with dist()

Remember we talked about our data being in n-dimensional space? Using those coordinates, there are a number of ways to calculate the distance between points. The simplest form is to calculate the euclidean distance between points using the generic formula:

\[d(p,q) = \sqrt{(p_{1}-q_{2})^2 + (p_{2}-q_{2})^2 + \ldots + (p_{n}-q_{n})^2}\]

but there are a number of other options as well. For instance you can also choose the maximum distance between two components (same dimension). The dist() function can generate these values for you using the parameter method to determine how the distance will be used. Your options are: euclidean, maximum, manhattan, canberra, binary or minkowski.

The dist() function will return a dist object which is a lower triangle distance matrix between all of the rows/observations using the columns as coordinates.

Let’s return to our PHU data in covid_demographics_norm.mx to try it out and see how it works.

dist(covid_demographics_norm.mx[,-1], 
     method="euclidean")  %>% str()# The default method is euclidean
Error in as.matrix(x): object 'covid_demographics_norm.mx' not found

1.5.2 Cluster your distance matrix

Now we are ready to cluster our distance matrix using the hclust() method

Parameters that are important:

  • method: already described as the method which we want to use to cluster our data.

    • ward.D, ward.D2, single, complete (default), average, mcquitty, median and centroid.
  • k: the number of groups we would like our final data to be split into - represented by colours.

# Make a cluster object and take a look at it
phu.hc <- hclust(dist(covid_demographics_norm.mx[,-1]), 
                 method = ...)
Error in eval(expr, envir, enclos): '...' used in an incorrect context
# What does our hclust object contain?
str(phu.hc)
Error in str(phu.hc): object 'phu.hc' not found

1.5.3 Plot your cluster object using fviz_dend() from the package factoextra

Now that we have generated our clustering object, you can see that it holds a number of features including the height (length) of our branches, an ordering of our PHUs and the order in which they merge. The merge process is described in more detail as well with the hclust() documentation

To simplify the plotting process, we can use the fviz_dend() function which will parse through the hclust object to produce a proper dendrogram. We can even specify some grouping or colouring schemes based on how many groups, k, we believe are present in our data.

We can treat the resulting plot like a ggplot to update particular elements as well.

# Plot out our dendrogram with fviz_dend

fviz_dend(...,                        # provide our hclust object
          cex = 1.5,                     # set the text size to be larger
          k = ...,                         # How many clusters do we expect in our data 
          palette = "Set1",              # what colours will we use. Many options including RBrewer palettes
          horiz = TRUE,
          labels_track_height = 12000,
         ) +

    # Bump up the text size for my old eyes
    theme(text = element_text(size = 20)) 
Error in eval(expr, envir, enclos): '...' used in an incorrect context

2.0.0 Clustering data into groups

So we’ve visualized our dataset as a dendrogram and it gives us an idea of how the samples might be related (in n-dimension space) based on the case values we produced from normalization.

K-means clustering is unsupervised learning for uncategorized data. We don’t really know the training labels of our data and are more interested in seeing which ones group together. How many clusters should we aim to generate? We’ve already seen that our data likely splits into 3 groups based on the heatmaps and hclust() data. Will we get the same thing with a k-means method?

When in doubt, you can quickly consult the factoMiner function fviz_nbclust() which can guess how many clusters are ideal for representing your data. Note that it may not produce your ideal number of clusters but if you’re stuck, this is a good start.

There are three method options: wss, gap_stat, and silhouette which all aim to minimize the number of clusters.

# How many clusters should we generate?
fviz_nbclust(x = covid_demographics_norm.mx[,-1],   # Provide the dataset
             FUNcluster =  kmeans,                  # How will you cluster the data?
             method=...) +                        # What method will you use?
  
  theme(text = element_text(size=10))
Error in eval(expr, envir, enclos): '...' used in an incorrect context
# How many clusters should we generate?
fviz_nbclust(x = covid_demographics_norm.mx[,-1],   # Provide the dataset
             FUNcluster =  kmeans,                  # How will you cluster the data?
             method="silhouette") +                 # What method will you use?
  
  theme(text = element_text(size=10))
Error in fviz_nbclust(x = covid_demographics_norm.mx[, -1], FUNcluster = kmeans, : object 'covid_demographics_norm.mx' not found
# How many clusters should we generate?
fviz_nbclust(x = covid_demographics_norm.mx[,-1],   # Provide the dataset
             FUNcluster =  kmeans,                  # How will you cluster the data?
             method="gap_stat") +                   # What method will you use?
  
  theme(text = element_text(size=10))
Error in fviz_nbclust(x = covid_demographics_norm.mx[, -1], FUNcluster = kmeans, : object 'covid_demographics_norm.mx' not found

2.1.0 Generate your clusters using kmeans()

So from three different analyses we didn’t really get a concensus on the best number of clusters:

  • wss: Our elbow level out at around k=3 or k=4

  • silhouette: Our biggest jump is at k=2 which is also our biggest value

  • gap_stat: The biggest jump in within-cluster distance is at k=3 although we see another jump again at k=5

but it looks like the answer ranges between 2 and 5 clusters. Note that if your data consistently suggest k = 1, then you shouldn’t cluster at all! Since our data already suggests 3 clusters, let’s start with that. We’ll use the kmeans() function to accomplish this.

Similar in idea to hclust(), the kmeans() algorithm attempts to generate k-partitioned groups from the data supplied with an emphasis on minimizing the sum of squares from points to the assigned cluster centers. This differs from hclust() which finishes building the entire relationship via dendrogram without actually choosing “clusters”.

We’re going to also use set.seed() for our random number generation. We tend to think of things as random, but computationally, randomness is built on algorithms. Therefore we can “recreate” our randomness if we use a pre-determined starting point also known as the “seed”.

The parameters we’re interested in using with kmeans() are:

  • x the numeric matrix of our data

  • centers determines the number of clusters we want to produce

  • nstart defines how many randomly chosen sets of k centres you’ll use to start the analysis. Choosing multiple different sets allows the algorithm to avoid local minima.

  • iter.max is the max number of iterations allowed while trying to converge on the best minimum metric

# Compute k-means with k = 3

# Set a seed for reproducibility.
set.seed(123)

# Generate our k-means analysis
phu_cases.km <- 
  kmeans(..., # We'll scale our data for this (more on that later too!)
         centers = 3, 
         nstart = 25,
         iter.max = 500)
Error in eval(expr, envir, enclos): '...' used in an incorrect context
# What is the structure of our kmeans object?
str(phu_cases.km)
Error in str(phu_cases.km): object 'phu_cases.km' not found
# K-means clusters showing the group of each individuals
phu_cases.km$...
Error in eval(expr, envir, enclos): object 'phu_cases.km' not found

2.2.0 Plot your k-means results with fviz_cluster()

Notice the structure of this output? It includes cluster which denotes which row belongs to each of the 3 clusters chosen. The centre of each cluster is defined by centers which in this case is a 3x7 array where each row uses 7 values to define their position within our 7-dimension dataset. We can see each cluster’s size is 18, 11, and 5 PHUs respectively. Notice, however, that none of the original coordinates for our data are retained in this object.

Rather than use ggplot directly, we’ll use the ggplot-compatible fviz_cluster() function to help us transform the values of our points from a 7-dimension coordinate system to a 2-dimension visual format suitable for our simpler brains. Under the hood fviz_cluster() is performing a principle component analysis to group our data along the first two principal components (more on what that means later too!).

The parameters we should be concerned with in this function include:

  • object the partitioning object for our data. In the case of k-means, it is our kmeans object.

  • data is the original dataset we used to generate the data. This will be necessary to plot the other data points.

  • ellipse.type determines how we’ll outline our clusters. This comes with a number of options including:

    • convex: draws boundaries based on the outer points in your cluster
    • confidence: produces a confidence ellipse around the cluster centres. This can be further adjusted using the parameter ellipse.level whose default is 0.95.
    • t, norm: assume multivariate t-distribution and multivariate normal distributions to produce their ellipses using ellipse.level to set their radius.
    • euclid: sets a circle of radius ellipse.level around the centre of each cluster.

Let’s see how our data has been partitioned by the k-means clustering.

# Plot the cluster
# You can treat this object like a ggplot object afterwards

phu_kmeans.plot <-

  fviz_cluster(object = ..., # our k-means object
               data = ..., # Our original data needed for PCA to visualize
               ellipse.type = "convex", 
               ggtheme = theme_bw(), 
               repel=TRUE, # Try to avoid overlapping text
               labelsize = 20,
               pointsize = 4,
               main = "K-means clustering of PHU by normalized case number"
               ) +

  # Set some ggplot theme information
  theme(text = element_text(size=20)) +

  # Set the colour and fill scheme to viridis
  scale_fill_viridis_d() +
  scale_colour_viridis_d()
Error in eval(expr, envir, enclos): '...' used in an incorrect context
# Print the plot!
phu_kmeans.plot
Error in eval(expr, envir, enclos): object 'phu_kmeans.plot' not found

2.3.0 Clustering your RNA-Seq data

Let’s return to the Wyler et al., 2020 readcount data and take a look at it through a different lens. Whereas before we had generated a heatmap of the data based on looking at genes expressed within a readcount range, we’ll now take a different approach.

Let’s look at the top 500 variable genes in the dataset to help cluster them. To accomplish that we’ll want to generate the standard deviation in read count for each row (gene). We’ll utilize a few verbs we haven’t used before in this class:

  • rowwise(): in the same class of functions as group_by(), this will subtly alter the tibble so that when we perform math operations on it, these will be completed on a row-by-row basis.

  • c_across(): this helper version of combine (c()) combines values across columns within our tibble.

# Review the wyler readcount data
head(wyler_readcounts.df)
Error in head(wyler_readcounts.df): object 'wyler_readcounts.df' not found
# Save our results into a new dataframe
wyler_readcounts_filtered.df <-
  
  # Start with the original read count data
  wyler_readcounts.df %>% 

  # Rename the columns by removing the first portion: AECII_xx
  rename_with(., ~ str_replace(string = .x, 
                               pattern = r"(\w*_\d*_)", 
                               replace = "")) %>% 

  # filter out the low readcount data. Low values will create wild variance easily
  filter(if_all(.cols = -1, .fns = ~ .x > 10)) %>% 

  # Prepare to do row-wise calculations
  ... %>% 

  # Calculate the standard deviation across rows
  mutate(stdev = sd(...)) %>% 
  
  # Ungroup and sort the data by descending value
  ungroup() %>% 
  arrange(desc(stdev)) %>% 
  
  # Take the top 500 most variable genes
  dplyr::slice(1:500)
Error in ...(.): could not find function "..."

Now we’ll just convert our dataframe into a matrix of values so we can perform our various analyses.

# Save just the RNA-Seq data into a matrix
wyler_readcounts.mx <- as.matrix(wyler_readcounts_filtered.df[, 3:38])
Error in as.matrix(wyler_readcounts_filtered.df[, 3:38]): object 'wyler_readcounts_filtered.df' not found
# Set the row names using the information from the data frame
rownames(wyler_readcounts.mx) <- wyler_readcounts_filtered.df$gene
Error in eval(expr, envir, enclos): object 'wyler_readcounts_filtered.df' not found
# Take a quick look at the resulting matrix and its properties
head(wyler_readcounts.mx)
Error in head(wyler_readcounts.mx): object 'wyler_readcounts.mx' not found
str(wyler_readcounts.mx)
Error in str(wyler_readcounts.mx): object 'wyler_readcounts.mx' not found

2.3.1 Create a heatmap of the most variable genes

Since we’re here, let’s take a quick step back and look at the heatmap from our new dataset. Does it reveal anthing to us now that it is more nuanced?

# Create a Heatmap object
wyler_hmap <- 
  Heatmap(...,               # Supply our matrix 
                      
          cluster_rows = TRUE, cluster_columns = TRUE,   # Cluster on both rows and columns
                      
          col = viridis(100),
                      
          # Use column_title as the title of our heatmap
          column_title = "Heatmap of RNA-Seq readcounts on top 500 most variable genes",
                      
          # Rotate the legend horizontally and give it a title
          heatmap_legend_param = list(title = "readcounts per gene",
                                      legend_direction = "vertical"),
                      
          # Remove the row names
          show_row_names = FALSE, 
                      
          # Set the dendrogram sizes
          ... = unit(40, "mm"),
          ... = unit(20, "mm")
                      
         )
Error in eval(expr, envir, enclos): '...' used in an incorrect context
# Plot the heatmap 
draw(wyler_hmap, 
     # Plot the legend on the bottom
     heatmap_legend_side = "left"
    )
Error in h(simpleError(msg, call)): error in evaluating the argument 'object' in selecting a method for function 'draw': object 'wyler_hmap' not found

2.3.2 Generate a k-means cluster analyis

Looks like the heatmap still doesn’t reveal a lot of information to us like how samples might be grouped. Let’s proceed with k-means and see if that works better. We just need to repeat our steps on a different set of data. Since we now have our most diverse genes and their readcounts, we’ll take a look at if we can cluster this information.

  1. Generate an estimate on the appropriate number of clusters
  2. Create our k-means cluster object
  3. Visualize the k-means object and see which experiments tend to group together.
# How many clusters should we generate?
fviz_nbclust(t(wyler_readcounts.mx), FUNcluster = kmeans, method="wss")
Error in t(wyler_readcounts.mx): object 'wyler_readcounts.mx' not found
fviz_nbclust(t(wyler_readcounts.mx), FUNcluster = kmeans, method="silhouette")
Error in t(wyler_readcounts.mx): object 'wyler_readcounts.mx' not found
fviz_nbclust(t(wyler_readcounts.mx), FUNcluster = kmeans, method="gap_stat")
Error in t(wyler_readcounts.mx): object 'wyler_readcounts.mx' not found

From the 3 methods we see that

  1. Our WSS method produced an elbow around k = 4

  2. Our silhouette method peaks at k = 4

  3. Our gap_stat method sees diminishing progress also at k = 4

Let’s proceed with that in mind!

# Compute k-means with k = 4

# Set a seed for reproducibility.
set.seed(123)

# Generate our k-means analysis
wyler_readcounts.km <- 
kmeans(scale(...), # We'll scale our data for this (more on that later too!)
       centers = 4, 
       nstart = 25,
       iter.max = 500)
Error in as.matrix(x): '...' used in an incorrect context
# Take a look at the resulting k-means object
str(wyler_readcounts.km)
Error in str(wyler_readcounts.km): object 'wyler_readcounts.km' not found
# Plot the cluster
# You can treat this object like a ggplot object afterwards

wyler_kmeans.plot <-

  fviz_cluster(object = ..., # our k-means object
               data = t(wyler_readcounts.mx), # Our original data needed for PCA to visualize
               ellipse.type = "convex", 
               ggtheme = theme_bw(), 
               repel=TRUE, # Try to avoid overlapping text
               labelsize = 20,
               pointsize = 4,
               main = "K-means clustering of filtered Wyler readcount data"
               ) +

  # Set some ggplot theme information
  theme(text = element_text(size=20)) +

  # Set the colour and fill scheme to viridis
  scale_fill_viridis_d() +
  scale_colour_viridis_d()
Error in eval(expr, envir, enclos): '...' used in an incorrect context
# Print the plot!
wyler_kmeans.plot
Error in eval(expr, envir, enclos): object 'wyler_kmeans.plot' not found

2.3.3 Interpreting our RNA-Seq clustering

Looking at the result, what’s nice about this kind of output is that we can immediately see the separation or clustering of our data. It looks like 4 was the way to go as there are 4 tight groupings from our data. It might be possible to further subdivide the group in the top left of our figure but it’s unlikely.

Based on our selection of genes, we observe:

  1. 24-, 48-, and 72-hour samples mock-treated (DMSO) and mock-infected, cluster together with 24-hour mock-treated samples infected with SARS-CoV-2.

  2. 48- and 72-hour samples mock-treated (DMSO) and infected by SARS-CoV-2 appear to share a similar profile.

  3. 24-hour samples treated with 200 nM 17AAG (HSP90 inhibitor) cluster together whether or not they are infected with SARS-CoV-2.

  4. 48- and 72-hour samples treated with 200 nM 17AAG cluster together whether or not they are infected with SARS-CoV-2.

These groupings suggest that perhaps the 24-hour SARS-CoV-infected timepoint is similar to uninfected controls. Meanwhile the 48- and 72-hour SARS-CoV-infected samples are similar but likely showing very distinct transcriptional changes due to infection. Treatment with the HSP90-inhibitor, however, appears to sufficiently alter expression profiles of infected and mock-infected cells to makes them cluster together. This would be a good starting point to begin digging further into the data.

Skeptic or believer? While our above analysis seems to make sense to us, it lacks a deeper understanding of what might be happening. For one thing, we haven’t even looked closely at the genes used to create our clustering. How many of them are relevant to the infection process? Is the clustering due to off-target effects of the HSP90 inhibitor (17AAG)? While clustering is an effective way to quickly identify if subgroups exist within your data, it’s important to follow up these findings with in-depth analyses of the data.

Yes the data clusters, but why does it cluster?


3.0.0 Dimension reduction trims the (data) fat

One problem we encountered in our above analysis of RNA-Seq data is that there were simply too many dimensions! With ~27k gene entries in our dataframe, using clustering to analyse the entire dataset would be memory-intensive if not impossible altogether. To circumvent this problem we attempted to subset our data in two ways - filtering by read counts, and comparing variance across datasets. These approaches were a form of dimensionality reduction - namely feature elimination. Our choices, however, may have inadvertently been throwing away data that could prove insightful! This is where other dimensionality reduction methods can help guide our analyses.

You can achieve dimensionality reduction in three general ways:

  1. Feature elimination: you can reduce the feature space by removing unhelpful features. No information is gained but you trim down your dataset size.

  2. Feature selection: on the reverse side you can simply choose the most important features to you. How will you decide? Some schemes include ranking your features by importance but this may suffer from information loss if incorrect features are chosen.

  3. Feature extraction: create new independent features which are a combination of the old features! Attempt to extract the essential features of your data in a more informationaly dense manner.

Technique Description Type Useful for
Random forest Popular machine learning algorithm for classification randomly chooses from subsets of features to classify data. The most frequently appearing features amongst the forests are chosen. Feature selection Only takes numeric inputs
PCA Principal component analysis attempts to maximize variation when transforming to a lower-dimensional space. All new features are independent of one another. Linear Feature Extraction Actually really good at finding outliers in RNAseq data
t-SNE t-Distributed Stochastic Neighbour Embedding is a non-linear technique similar to PCA suitable for high-dimension datasets. It attempts to minimize probabilities in mirroring nearest neighbour relationships in transforming from high to low-dimension spaces Non-linear Feature extraction Determine if your data has underlying structure
UMAP Uniform manifold approximation and projection is projection-based like t-SNE, this technique attempts to preserve local data structure but has improved translation of global data structure. Non-linear feature extraction Faster than t-SNE

Since we’re not exactly building a classifier but rather trying to find trends in our data, we won’t be looking at Random Forests here. Here we are interested in exploratory data analysis. We have a data set we want to understand, sometimes it is too complex to just project or divine 1) the underlying structure and 2) the features that drive that structure.

There’s more to explore with dimension reduction: The above table is just a small subset of potentially different kinds of dimension reduction methods. PCA for instance has 2 “variants”: Multiple Correspondence Analysis (MCA) which deals with relationships between categorical variables rather than continuous ones, and Independent Component Analysis (ICA) which also uses linear dimension reduction to identify independent components in your dataset. You can check out a little more here and over here


3.1.0 Reduce your dimensionality with PCA

Going back to our PHU data, we used 7 dimensions to classify our data but what if we wanted to transform that information in some way to reduce the number of dimensions needed to represent their relationships? For our data set, 7 dimensions isn’t a lot of features but in other cases (like RNA-Seq) you might encounter feature-dense datasets and without knowing a priori which ones are meaningful, PCA provides a path forward in classifying our data.

Now there are caveats to PCA. It produces linear feature extraction by building new features in your n-dimensional space such that each new feature (kind of like a projected line) maximizes variance to the original data points. Each new component must be uncorrelated with (ie perpendicular to) the previous ones while accounting for the next highest amount of variance. More resources on how this works in the references.

How do we go about maximizing the variance (spread of red dots) to our data features? Finding the point where variance is maximized, also minimizes error (red line lengths). Generated by user Amoeba on stackexchange.com

All math aside, our goal is to reduce our feature set to something smaller by trying to represent our data with these new features. Just remember that highly variable data and outliers can dominate your principal components.

For simplicity let’s head back and look at our PHU age group data again. To illustrate our example with PCA, let’s use the original data normalized by PHU population.

# View the normalized PHU age group data
head(covid_demographics_norm.mx)
Error in head(covid_demographics_norm.mx): object 'covid_demographics_norm.mx' not found

3.2.0 Use PCA() to generate our analysis

To generate our analysis of the PHU data, we’ll use the FactoMineR function PCA() for which there are some parameters we’ll be using that we should discuss: - X a data frame of n rows (observations) and p columns (numeric variables) - ncp the number of dimensions kept in the results (5 is the default) - scale.unit a boolean to scale your data (TRUE by default)

3.2.1 What does it mean to scale our data?

Remember that PCA is trying to maximize distance between a principle component and all of the observations supplied. Depending on the nature of your variables you may have, for instance, two different unit types like height and mass. Smaller changes in height may be matched with much larger changes in mass or just wider overall variance. This may lead the PCA algorithm to prioritize mass over height when you’d prefer they have an equal importance. By centering your mean and scaling data to unit variance, everything is compared as a z-score, bringing the overall variance across a variable to within ~3 standard deviations.

Let’s compare PCA with and without scaling shall we?

# Build a PCA of our PHU data with scaling applied
phu_scaled.pca <- PCA(..., 
                      scale.unit = ..., # What happens when we don't scale the data?
                      ncp = ...,
                      graph = TRUE)
Error in PCA(..., scale.unit = ..., ncp = ..., graph = TRUE): could not find function "PCA"
# Build a PCA of our PHU data WITHOUT scaling applied
phu_unscaled.pca <- PCA(covid_demographics_norm.mx[,-1], 
                        scale.unit = ...,
                        ncp = 7,
                        graph = TRUE)
Error in PCA(covid_demographics_norm.mx[, -1], scale.unit = ..., ncp = 7, : could not find function "PCA"
# Take a look at the information inside our PCA object
print(...)
Error in eval(expr, envir, enclos): '...' used in an incorrect context

3.2.2 Our PCA object has a complex number of data pieces

Regardless of scaling, the result of our PCA() call produces an object with many variables we can access. Above you can see a brief description for each variable but we are most interested in a few particular ones:

  • eig holds our dimensional data but also describes just how much each new principle component describes the overall variation of our data.

  • var holds the results of all the variables. We can use these to graph and visualize our data.

  • ind$coord will allow us to plot the coordinates of our observations along the principal components.


3.3.0 Use the eigenvalues to determine the percent variance of each component

The eigenvalues from our analysis pair with the eigenvectors (principle components) to help transform our data from the original feature set to the new set of features. While the eigenvectors may determine the directions of the new feature space, the eigenvalue represents the magnitude of the vector and in this case can be used to calculate the percent of overall variance explained by our eigenvector.

The important take-away is that we can now see just how much of our variance is explained in each new principle component. We can access this information directly from phu_scaled.pca or by using the function get_eigenvalue(). We can also plot this as a barchart instead using fviz_eig().

# Look at our eigenvalues directly
phu_scaled.pca$...
Error in eval(expr, envir, enclos): object 'phu_scaled.pca' not found
# Use get_eigenvalue() to look at our eigenvalues
get_eigenvalue(phu_scaled.pca)
Error in get_eig(X): object 'phu_scaled.pca' not found

3.3.1 Build a scree plot to look at the effect of your dimensions

See how nearly 80% of our variation is explained in just our first dimension? Let’s use fviz_eig() to display this information visually in what is known as a scree plot. It’s essentially a barplot/lineplot combo but what we’re interested in is following the lines much like our cluster-estimating WSS method. We use a “sharp elbow” to determine how many principal components we need.

# Visualize the impact of our eigenvalues
fviz_eig(..., addlabels = TRUE) + theme(text = element_text(size=10))
Error in eval(expr, envir, enclos): '...' used in an incorrect context

3.3.2 Most of our variance is accounted for in the first two principle components!

Looking at our eigenvalues we can see that even though 7 new PCs were generated, the first two explain almost 92% of our variance. That suggests that whatever linear separation in our data exists, we can recreate in a two-dimensional projection of coordinates from PC1 and PC2. This suggests that we can recreate the underlying structure of our data with these two new features instead of using a 7-dimensional space!

This makes some sense when we take a closer look at the data. For instance, there isn’t much visual information found in our 0 to 4 age range. The small distribution of information in these features may not do much, in the grand scheme, to change how the PHUs are separated from each other. Then again, perhaps they do contain information that we simply cannot see easily! How will we know?


3.4.0 Investigate your variables with get_pca_var()

Within our PCA, we can access information regarding how our original variables are transformed into the new space. This is all stored in the var element of our PCA object. We can extract the aspects of this using the get_pca_var() and visualize these to determine the quality of our variables and their representation by the new principal components.

# What is the information associated with our original variables
phu.var <- get_pca_var(...)
Error in eval(expr, envir, enclos): '...' used in an incorrect context
phu.var
Error in eval(expr, envir, enclos): object 'phu.var' not found

3.4.1 What is the contribution of each variable to the new components?

Each original variable may, to some degree, influence the amount of information captured into each new principal component. When we look at each we can see the breakdown of variables, which in some cases, can reveal to us where more or less variation is found as well.

# What is the contribution of each variable?
phu.var$...
Error in eval(expr, envir, enclos): object 'phu.var' not found

From above we can see that our original variables equally contribute to our first principal component but there is much more contribution in PC2 from three variables: 0 to 4, 5 to 11 and 80+. From our previous scree plot that means that 12.8% of overall variation, which is described in PC2, is mainly from these three groups.

How many dimensions should I get and how many do I keep? It is at this point that we should discuss the maximum number of possible dimensions. Given n observations, and p features, the maximum number of dimensions after reduction is min(n-1, p). In terms of how many dimensions you keep, you should consider the general rule of thumb that at least 80% of your variation should be accounted for by the principal components that you choose. Keep that in mind!


3.4.2 Checking the quality and representation of your variables in the PCA with coord and cor

From the remaining variable information, coord and cor can both be used to plot our original variables along different pairs of principal components. These values are the same because this is not a projection of our variables onto the PCs but a correlation of them! The distance between the origin and the variable coordinates gauges the quality of the variables on the map. This number is summarized in the cos2 (squared cosine) element.

# Look at the coordinates of our variables across the PCs.
phu.var$...
Error in eval(expr, envir, enclos): object 'phu.var' not found
phu.var$...
Error in eval(expr, envir, enclos): object 'phu.var' not found
# What is the quality of our variables?
# The higher the value the better!
phu.var$...
Error in eval(expr, envir, enclos): object 'phu.var' not found

3.4.3 Plot your variable information on a unit circle with fviz_pca_var()

To capture all of our information in a single visualization we can use fviz_pca_var() to assess our variables. The fviz_pca_var() function will plot cos2 values on a 2-dimension axis of your choosing. High quality variables represented by the two dimensions of your circle (ie PC1 vs PC2) will be closer to the circumference of the circle.

Some variables may require more than 2 components to represent the data and they will therefore fall inside the circle. Low quality correlations will be closer to the origin since they correlate less with the two dimensions represented on the graph.

As we will verify from above, most of our variables are positively correlated with PC1. Across both PC1/PC2, our variables correlate highly.

Two important parameters to keep in mind:

  • X: the PCA object that you want to create.

  • axes: a numeric vector with the two dimensions you want to examine.

# Compare how our variables contribute and correlate with PC1/PC2
fviz_pca_var(X = phu_scaled.pca, 
             col.var = ..., # How will we colour our data/lines
             gradient.cols = c("green", "yellow", "red"), 
             labelsize = 6,
             repel = TRUE, # make sure text doesn't overlap
             axes = c(1,2) # Determine which PCs you want to graph
            ) + 
    theme(text = element_text(size=10))
Error in eval(expr, envir, enclos): '...' used in an incorrect context
# Compare how our variables contribute and correlate with PC2/PC3
fviz_pca_var(phu_scaled.pca, 
             col.var = "contrib", # How will we colour our data/lines
             gradient.cols = c("green", "yellow", "red"), 
             labelsize = 6,
             repel = TRUE, # make sure text doesn't overlap
             axes = c(...) # Determine which PCs you want to graph
            ) + 
    theme(text = element_text(size=10))
Error in .check_axes(axes, .length = 2): '...' used in an incorrect context

3.5.0 Check if PHUs cluster or separate with fviz_pca_ind()

Now that we’ve generated our PCA, let’s see if there is any clear separation between our PHUs across the first two dimensions of new features. Much like our variables, all of the individual coordinate data can be found in our ind element of our PCA object. Within there, we’ll find the coord information so we could directly build a ggplot with phu_scaled.pca$ind$coord BUT there’s a simpler way.

We’ll parse through the PCA object with fviz_pca_ind() to plot our data along the first two principle coordinates. We can define pointsize and col.ind to help add some population size information to our PHUs.

# Graph our scaled PCA data.
test <-
fviz_pca_ind(..., 
             pointsize = covid_demographics_norm.mx[,1], # Set point size and colour by PHU population
             col.ind = log10(covid_demographics_norm.mx[,1]),
             repel = TRUE, # avoid overlapping text points
             labelsize = 5, 
             axes = c(1,2)
            ) + 
    
    theme(text = element_text(size=10)) + # Make our text larger

    scale_size(range = c(3, 10)) + # Update the point size range

    scale_colour_viridis_c() # Change the colour scheme
Error in eval(expr, envir, enclos): '...' used in an incorrect context
test
Error in eval(expr, envir, enclos): object 'test' not found
# Graph our UNscaled PCA data.

fviz_pca_ind(..., 
             pointsize = covid_demographics_norm.mx[,1], # Set point size and colour by PHU population
             col.ind = log10(covid_demographics_norm.mx[,1]),
             repel = TRUE, # avoid overlapping text points
             labelsize = 5, 
             axes = c(1,2)
            ) + 
    
    theme(text = element_text(size=10)) + # Make our text larger

    scale_size(range = c(3, 10)) + # Update the point size range

    scale_colour_viridis_c() # Change the colour scheme
Error in eval(expr, envir, enclos): '...' used in an incorrect context
# What are the contributions of each age group to the various unscaled dimensions
phu_unscaled.pca$var$contrib
Error in eval(expr, envir, enclos): object 'phu_unscaled.pca' not found
# Look at our unscaled eigenvalues directly
phu_unscaled.pca$eig
Error in eval(expr, envir, enclos): object 'phu_unscaled.pca' not found

3.5.1 Scaled vs unscaled data

As you can see from our graphing of both PCA sets, when we choose not to scale our data something a little more drastic happens. PC1’s portion of variance increases to a 83.2% accounting of overall variance. We see many of our points move and separation between some of our PHUs is increased (Kingston/Frontenac and Peel). These changes are likely due to the larger range of values from the 20 to 39 age group which now contributes to 31% of PC1’s variation.

So think carefully about your features and what they represent. Depending on their range, and unit values, you may be inadvertently weighing them more than your other features! Scaling adjusts your data so that all features are weighted equally!

3.6.0 Can we cluster our transformed data?

Now that we’ve effectively reduced the complexity of our data, can we produce the same k-means clustering as before? Let’s try to cluster just on the two dimensions presented, which we have already graphed!

# How many clusters should we use based on our PCA data?
fviz_nbclust(phu_scaled.pca$ind$coord[,1:2], FUNcluster = kmeans, method="wss")
Error in fviz_nbclust(phu_scaled.pca$ind$coord[, 1:2], FUNcluster = kmeans, : object 'phu_scaled.pca' not found
fviz_nbclust(phu_scaled.pca$ind$coord[,1:2], FUNcluster = kmeans, method="silhouette")
Error in fviz_nbclust(phu_scaled.pca$ind$coord[, 1:2], FUNcluster = kmeans, : object 'phu_scaled.pca' not found
# Generate our k-means analysis and visualize it

set.seed(123)

phu_PCA_kmeans.plot <-

    kmeans(..., centers = 3) %>% 

    fviz_cluster(., # our k-means object
                 data = ..., # Our original data needed for PCA to visualize
                 ellipse.type = "convex", 
                 ggtheme = theme_bw(), 
                 repel=TRUE, # Try to avoid overlapping text
                 labelsize = 20,
                 pointsize = 4,
                 main = "K-means clustering of PHU by normalized case number AFTER Principal Component Analysis"
                 ) +

    # Set some ggplot theme information
    theme(text = element_text(size=20)) +

    # Set the colour and fill scheme to viridis
    scale_fill_viridis_d() +
    scale_colour_viridis_d()
Error in kmeans(..., centers = 3) %>% fviz_cluster(., data = ..., ellipse.type = "convex", : '...' used in an incorrect context
# phu_pca_kmeans.plot
# Plot both of our figures together
fig.show="hold"; out.width="50%"; out.height = "50%"
phu_kmeans.plot
Error in eval(expr, envir, enclos): object 'phu_kmeans.plot' not found
phu_PCA_kmeans.plot
Error in eval(expr, envir, enclos): object 'phu_PCA_kmeans.plot' not found

3.6.1 Was it worth it to use PCA?

From our estimations, the clustering choices look near identical and this is not exactly the best dataset to work with since it is rather small but you can see that we have now transformed our data into just 2 features! Could we do the same using just 2 features from our original dataset? No! So imagine transforming a much larger and feature-heavy dataset into a smaller number of features. From the data, we are also able to discern which of our original features may really contribute to each new principal component!

Something to consider the next time you’re working with a large data set!


3.7.0 PCA on a large RNA-Seq dataset

Now that we’ve walked through how to generate a PCA for a simpler dataset, let’s return to our RNA-Seq readcount data. We won’t trim down the data at all but rather just provide the entire dataset as a matrix for feature reduction. Let’s review the steps:

  1. Generate a matrix of your data, naming the columns and rows by whatever information you might have. Make sure your columns represent your features, and rows are observations.
  2. Create the PCA object.
  3. Review your eigenvalues and eigenvectors to assess how well your reduction worked. Determine how many dimensions you wish to use in further analysis.
  4. Calculate the number of possible clusters.
  5. Plot the results.
# 1. Generate our matrix from the readcount data
wyler_readcounts_all.mx <-

  wyler_readcounts.df %>% 
  # Rename the columns by removing the first portion: AECII_xx
  rename_with(., ~ str_replace(string = .x, 
                               pattern = r"(\w*_\d*_)", 
                               replace = "")) %>% 

  # Keep only the experimental observations (how many are there?)
  dplyr::select(c(3:38)) %>% 

  # Convert to a matrix
  as.matrix() %>% 

  # Transpose the matrix so our columns are now genes
  ...
Error in eval(expr, envir, enclos): '...' used in an incorrect context
# Name the rows of the matrix
colnames(wyler_readcounts_all.mx) <- wyler_readcounts.df$gene
Error in eval(expr, envir, enclos): object 'wyler_readcounts.df' not found
# We'll peek at the structure of the matrix. Looking at it with head() will be messy.
str(wyler_readcounts_all.mx)
Error in str(wyler_readcounts_all.mx): object 'wyler_readcounts_all.mx' not found
# 2. Generate the PCA object
readcounts_scaled.pca <- PCA(..., 
                             scale.unit = TRUE, # What happens when we don't scale the data?
                             ncp = 40,
                             graph = TRUE)
Error in PCA(..., scale.unit = TRUE, ncp = 40, graph = TRUE): could not find function "PCA"
# 3. Review how much variance is explained by the new components
readcounts_scaled.pca$eig
Error in eval(expr, envir, enclos): object 'readcounts_scaled.pca' not found
# 4. How many clusters should we use based on our PCA data?
fviz_nbclust(readcounts_scaled.pca$ind$coord[,1:2], FUNcluster = kmeans, method="silhouette")
Error in fviz_nbclust(readcounts_scaled.pca$ind$coord[, 1:2], FUNcluster = kmeans, : object 'readcounts_scaled.pca' not found
# 5. Generate our k-means analysis and visualize it
set.seed(123)

readcounts_PCA_kmeans.plot <-

  # Let's go with 4 clusters in order to compare to our original version.
  # This is close to the silhouette value as well.
  kmeans(readcounts_scaled.pca$ind$coord[,1:6], centers = ..., ) %>% 

  fviz_cluster(., # our k-means object
               data = readcounts_scaled.pca$ind$coord[,1:2], # Our original data needed for PCA to visualize
               ellipse.type = "convex", 
               ggtheme = theme_bw(), 
               repel=TRUE, # Try to avoid overlapping text
               labelsize = 20,
               pointsize = 4,
               main = "K-means clustering of PHU by normalized case number AFTER Principal Component Analysis"
               ) +

  # Set some ggplot theme information
  theme(text = element_text(size=10)) +

  # Set the colour and fill scheme to viridis
  scale_fill_viridis_d() +
  scale_colour_viridis_d()
Error in fviz_cluster(., data = readcounts_scaled.pca$ind$coord[, 1:2], : '...' used in an incorrect context
# Look at our original plot with 4 centres
wyler_kmeans.plot
Error in eval(expr, envir, enclos): object 'wyler_kmeans.plot' not found
# Versus our PCA with 4 centres
readcounts_PCA_kmeans.plot
Error in eval(expr, envir, enclos): object 'readcounts_PCA_kmeans.plot' not found

If we were to compare our two versions of the k-means clustering using raw data vs our PCA dimension-reduced data, the results are pretty similar although not quite. I’ve also added what your data would look like if you did NOT scale the data before performing PCA.

Aspect Raw K-means PCA K-means PCA K-means PCA K-means
Scaled Scale at time of k-means Scale at time of PCA Scale at time of PCA Unscaled at time of PCA
Features used for k-means 27K genes Top 2 components (43% var) Top 6 components (67% var) Top 2 components (73 % var)
Dim 1 44.6% 26.1% 26.1% 50.3%
Dim 2 17.8% 16.9% 16.9% 22.7%
Cluster 1

24h: all, DMSO-treated;

48h+72h: uninfected, DMSO

24h+48h: SARS-CoV2 infection, DMSO;

Some 24h+48h: uninfected, DMSO

24h: all, DMSO-treated;

48h+72h: uninfected, DMSO

24h: all, DMSO-treated;

48h+72h: uninfected, DMSO

Cluster 2 48h+72h: SARS-CoV2 infection, DMSO-treated

48h: uninfected, DMSO;

72h: all, DMSO-treated

48h+72h: SARS-CoV2 infection, DMSO-treated 48h+72h: SARS-CoV2 infection, DMSO-treated
Cluster 3 24h: 17AAG-treated 24h: 17AAG-treated 24h: 17AAG-treated 24h: 17AAG-treated
Cluster 4 48h+72h: 17AAG-treated 48h+72h: 17AAG-treated 48h+72h: 17AAG-treated 48h+72h: 17AAG-treated

Some interesting conclusions we can draw from this are

  1. Whether our data is scaled just prior to performing k-means, scaled or unscaled with PCA, we see that our 17AAG-treated samples have 2 main clusters regardless of infection status. Either 24h or 48h+72h data. There is likely a strong effect on expression by the 17AAG treatment that is much stronger than the infection by SARS-CoV2. In fact, if we were to explore this further we would see that if we created more clusters, we would find that time plays a bigger role in further separating these groups than SARS-CoV2 infection.
  2. Choosing the number of features does matter even after PCA! Looking at just our scaled PCA data to create a k-mean clustering, we can’t get good concordance with our raw data analysis using only our top two PCA features which account for just 43% of variance. On the other hand, if we choose our top six features for k-means clustering which account for 67% of variance, we do get the same clusters returned, albeit a slighlty different looking project.

Therefore PCA can help identify factors which heavily influence your samples as long as you also correctly choose enough features to represent your transformed data.


3.8.0 Restart R!

At this point, we’ve accumulated a lot of data in memory. We won’t need our previous data anymore so let’s restart R and reload the libraries we’ll need. You can

# Packages to help tidy our data
library(tidyverse)
library(readxl)
library(magrittr)

# Packages for the graphical analysis section
library(viridis)
library(RColorBrewer)

# Data projection packages
library(Rtsne)
library(umap)

4.0.0 Non-linear projection

How do we identify trends or groups within deeply complex data in an unsupervised manner?

So we just spent most of our time trying to understand how to transform our sets based on the large amounts of variation in our data. There are some limitations we’ve discussed but in some cases, depending on your dataset size you may be interested in finding small, local similarities rather than the large variation that comes with PCA.

There are two popular projections we’ll discuss today but first let’s put together a more complex dataset based on some RNAseq data in GSE150316_DeseqNormCounts_final.txt and it’s companion file 2020.07.30.20165241-supp_tables.xlsx

# Read in our RNAseq data
tissue_data.df <- ...(file = ...,
                             header = TRUE,
                             row.names = 1)
Error in ...(file = ..., header = TRUE, row.names = 1): could not find function "..."
# Take a quick look at it
head(tissue_data.df)
Error in head(tissue_data.df): object 'tissue_data.df' not found
dim(tissue_data.df)
Error in eval(expr, envir, enclos): object 'tissue_data.df' not found
# Read in some additional patient data
patient_data.df <- read_excel("./data/2020.07.30.20165241-supp_tables.xlsx", sheet=2)

# Take a quick look at it
head(patient_data.df)
dim(patient_data.df)
[1] 24 18

4.0.1 Why can’t we use PCA on this dataset?

Before we attempt one of our non-linear projection methods, let’s try and use some of the methods we’ve spent all this time looking at. We’ll naively repeat our PCA steps on this tissue data and see if we can pull any information from it. We know there are 10 tissue groups in our data so we’ll use that as a basis for clustering.


As we can see from the above plot, a majority of our samples from various tissue types fall into a single cluster. What’s more, as we’ll see, there is some structure in these samples as multiple samples come from the same patient. Sometimes even the same tissue! These relationships may have a large effect in the overall variation we see in the data, making it hard to distinguish between even tissue types. This is why we’ll turn to non-linear projection and see if it can help us out. Before that we’ll need to do some more wrangling…

4.0.2 Reformat our patient meta data

First let’s examine our patient_data.df which has 24 patient samples (aka cases) listed in total. These 24 cases relate back to tissue_data.df which have variables representing different combinations of case and tissue type and observations for some 59K transcripts.

At this point we want to reformat the column names in patient_data.df a bit before selecting for just the viral load and viral load percentage information located in the 2nd and 3rd column. We’ll hold onto this in patient_viral_load.df for later use.

# Create a dataframe holding just the viral_load information for each patient
patient_viral_load.df <-
  patient_data.df %>% 

  # Retain just the first 3 columns
  dplyr::select(1:3) %>% 

  # Rename the 2nd and 3rd columns
  ...(case_num = 1, viral_load = 2, viral_load_percent = 3)
Error in ...(., case_num = 1, viral_load = 2, viral_load_percent = 3): could not find function "..."
head(patient_viral_load.df)
Error in head(patient_viral_load.df): object 'patient_viral_load.df' not found

4.0.3 Prepare our RNAseq tissue data for analysis by eliminating features

We now want to format tissue_data.df much in the way we did with our PHU data. We want to convert our current data which lists genes as observations and tissue samples as columns. Essentially, we’d like to transpose this and we could do that but the transposition converts everything to a matrix. In the end, we want to work with a data frame so we can hold more information.

To reduce on memory and runtime, however, we should trim our dataset. We aren’t really interested in looking at 59,090 genes - many of which may be barely expressed. Since these are normalized counts across the dataset, we can filter out low-expression genes to make tissue_data_filtered.df. Yes, this would again be considered a form of feature elimination. In general we will:

  1. Convert the row names to their own column.
  2. Calculate the mean of the normalized counts across each observation and filter for a minimum of 0.5.
  3. Calculate the variance across each observation as a backup plan.
system.time(
# Trim the tissue data down
tissue_data_filtered.df <-

  tissue_data.df %>% 

  # Convert the row names to an actual column
  rownames_to_column(var="gene") %>% 

  # Set up the table to perform row-wise operations
  rowwise() %>% 

  # Calculate the mean expression of each gene across all tissue samples
  mutate(mean = ...(c_across(where(is.numeric)))) %>% 

  # Filter for samples with low expression
  filter(mean > 0.5) %>% 

  # Calculate overall variance in case we need to make our dataset smaller
  mutate(variance = ...(c_across(where(is.numeric)))) %>% 

  # Arrange samples by descending variance
  arrange(desc(variance)) %>% 

  # Remove the grouping specification
  ungroup()

)
Error in rownames_to_column(., var = "gene"): object 'tissue_data.df' not found
Timing stopped at: 0 0 0
# Take a look at the final results
head(tissue_data_filtered.df)
Error in head(tissue_data_filtered.df): object 'tissue_data_filtered.df' not found
# how big is our filtered data frame?
dim(tissue_data_filtered.df)
Error in eval(expr, envir, enclos): object 'tissue_data_filtered.df' not found

4.0.4 More data wrangling to transpose our RNAseq data

Now that we’ve filtered our data down to ~29k genes, we’ll run through two more steps:

  1. We’ll transpose our data using a combination of pivot_longer() and pivot_wider().
  2. We’ll use a series of string matches and joins to add some sample information to our data. It won’t be used for our analysis but will be used when we plot the results!
  3. Lastly we’ll convert the relevant parts of our data frame to a matrix. There is a huge memory savings in the algorithm when working with a matrix and you have limited memory on your RStudio Hubs!
# You need to transpose the data. 
# We can do it with dplyr to keep it as a data frame and to add some info

tissue_RNAseq.df <-

  tissue_data_filtered.df %>% 
  
  # trim down the columns to drop mean/variance
  dplyr::select(1:89) %>% 

  # pivot longer
  pivot_longer(cols=c(2:89), names_to = ..., values_to = ...) %>% 

  # redistribute the gene names to columns
  pivot_wider(names_from = gene, values_from = ...)
Error in tissue_data_filtered.df %>% dplyr::select(1:89) %>% pivot_longer(cols = c(2:89), : '...' used in an incorrect context
# We want to add some additional sample information before assessing the data

tissue_RNAseq.df <-

  tissue_RNAseq.df %>% 

  # Grab just the sample names
  dplyr::select(sample) %>% 

  # Grab information from it like case number, tissue, and tissue number
  # takes the form of "caseX.tissueY" or "caseX.tissue.NYC" or "NegControlX"
  # Remember that this returns a LIST of character matrices
  str_match_all(., pattern=r"(case([\w]+)\.([a-z]+)([\d|\.NYC]*)|(NegControl\d))") %>% 

  # Bind all the matrices from all the list elements together in a single object (likely a matrix)
  do.call(rbind, .) %>% 

  # Convert the results to a data frame
  as.data.frame() %>% 

  # Rename the columns based on the capture groups
  dplyr::rename(., sample = V1, case_num = V2, tissue = V3, tissue_num = V4, neg_num = V5) %>%

  # Coalesce some of the info due to negative control samples and clean up a column
  mutate(case_num = coalesce(case_num, neg_num),
         tissue_num = str_replace_all(.$tissue_num, pattern = "\\.", replace = "")) %>%
  
  # Drop the neg_num column
  dplyr::select(1:4) %>%
  
  # Join this result to the RNAseq info
  full_join(., y=tissue_RNAseq.df, by=c("sample" = "sample")) %>%
  
  # Join that result to grab viral load information
  right_join(patient_viral_load.df, y=., by=c("case_num" = "case_num"))
Error in right_join(patient_viral_load.df, y = ., by = c(case_num = "case_num")): object 'patient_viral_load.df' not found
# Look at the resulting dataframe
head(tissue_RNAseq.df)
Error in head(tissue_RNAseq.df): object 'tissue_RNAseq.df' not found
# How many tissue types do we have?
table(tissue_RNAseq.df$tissue)
Error in table(tissue_RNAseq.df$tissue): object 'tissue_RNAseq.df' not found
# Generate a matrix version of our data but drop the sample metadata!
tissue_RNAseq.mx <- as.matrix(tissue_RNAseq.df[,...])
Error in as.matrix(tissue_RNAseq.df[, ...]): object 'tissue_RNAseq.df' not found
# head(tissue_RNAseq.mx)
str(tissue_RNAseq.mx)
Error in str(tissue_RNAseq.mx): object 'tissue_RNAseq.mx' not found
dim(tissue_RNAseq.mx)
Error in eval(expr, envir, enclos): object 'tissue_RNAseq.mx' not found

4.1.0 t-Distributed Stochastic Neighbour Embedding with the Rtsne package

We now have a somewhat more complex dataset. We are still short on actual samples (now observations) but 88 observations and nearly 30K features isn’t so bad. A broad question we may wish to ask with such a data set is if there is an underlying structure to these samples - i.e. do we see grouping based on tissue type, or perhaps even sample preparation.

t-Distributed Stochastic Neighbour Embedding or t-SNE is a way for us to project our high-dimension data onto a lower dimension with the aim at preserving the local similarities rather than global disparity. When looking at data points, t-SNE will attempt to preserve the local neighbouring structure. As the algorithm pours over the data, it can use different transformations for different regions as it attempts to transform everything to a lower dimension. It is considered “incredibly flexible” at finding local structure where other algorithms may not.

This flexibility is accomplished through 2 steps:

  1. Reduce dimensionality of your data features with PCA!
  2. Generate a probability distribution between all pairs by making similar objects highly probably and assigning dissimilar objects a low probability.
  3. Define a similar probability distribution for the samples in a lower dimension while minimizing the divergence between the two distributions based on a distance metric between points in the lower dimension.

We’ll discuss more about how this algorithm affects interpretation after seeing the results, but this is considered an exploratory data visualization tool, rather than explanatory.

To produce our t-SNE projection we’ll use the Rtsne() function from the package of the same name. Some important parameters are:

  • X is our data matrix where each row is an observation

  • dims sets the number of dimensions we’d like to project onto (default is 2).

  • initial_dims sets the number of dimensions that should be retained in the initial PCA step (Default 50).

  • perplexity a numeric parameter that tunes between local and global aspects of your data.

    • This parameter is a guess as to how many close neighbours a point may have. If you have a sense of sample types (or clusters!) ahead of time, you could try to play with this value (default is 30).

    • According to the algorithm this value should follow this rule: \(perplexity * 3 \lt nrow(X) -1\)

  • pca_scale is a boolean to set if the initial PCA step should use scaled data.

  • max_iter is the maximum number of iterations in the algorithm (default is 1000).

# Rtsne prefers using a matrix for memory issues
# set a seed for reproducible results
set.seed(2024)

# Try for perplexity of 30 can go as high as 29 before crash
# We have just 90 samples, but between 1-52 samples per "group"
tissue_tsne <- Rtsne(..., 
                     dims=2, 
                     perplexity=5, 
                     verbose=TRUE, 
                     pca_scale = TRUE,
                     max_iter = 1500) 
Error in eval(expr, envir, enclos): '...' used in an incorrect context
# What does our t-SNE object look like?
str(...)
Error in eval(expr, envir, enclos): '...' used in an incorrect context

4.1.1 Extract information from our tsne object

Looking above at the result of our t-SNE analysis, we can notice a few things…

  1. We get back the number of objects we put in: 88.
  2. The element Y is an 88x2 matrix holding the coordinates for our new projection.
  3. There are no eigenvectors or other information about the variables or dimensions or how they were used in the projection!

That’s right, there is no underlying information for mapping back to our original dataset. It’s a completely black box with no way to reverse engineer the process. That’s because the process itself is stochastic! Whereas PCA was a deterministic process - repeatable the same way every time with the same data - that is not the case for t-SNE. That’s why even though it can be quite powerful in identifying local similarities, t-SNE does not provide a mathematical pathway back to our original data!

Let’s extract the information, combine it with our sample information and project it using ggplot2. We can do this with a scatterplot since we have a set of x/y coordinates we can work with.

# Build our new data frame from the Y values
tissue_tsne.df <- data.frame(x.coord = ...,
                             y.coord = ...)
Error in eval(expr, envir, enclos): '...' used in an incorrect context
# Add in our sample information
tissue_tsne.df <- cbind(tissue_RNAseq.df[,1:6], tissue_tsne.df)
Error in cbind(tissue_RNAseq.df[, 1:6], tissue_tsne.df): object 'tissue_RNAseq.df' not found
# Fix up the information just a little bit to remove NA viral load information
tissue_tsne.df <-
  tissue_tsne.df %>% 
  # replace NAs with DNW (did not work)
  mutate(viral_load = replace_na(viral_load, replace = "DNW"))
Error in mutate(., viral_load = replace_na(viral_load, replace = "DNW")): object 'tissue_tsne.df' not found
head(tissue_tsne.df)
Error in head(tissue_tsne.df): object 'tissue_tsne.df' not found
combo.colours = c(brewer.pal(12, "Paired"), brewer.pal(12, "Set3"), brewer.pal(8, "Set1"))

# 1. Data
ggplot(data = tissue_tsne.df) +
  # 2. Aesthetics
  aes(x = x.coord, 
      y = y.coord, 
      colour = ...) +

  # Themes
  theme_bw() +
  theme(text = element_text(size=20)) +

  # 3. Scaling
  scale_colour_manual(values = combo.colours) +

  # 4. Geoms
  geom_text(aes(label = case_num), size = 10)
Error in ggplot(data = tissue_tsne.df): object 'tissue_tsne.df' not found

4.1.2 Interpreting our t-SNE plot

While we don’t have a lot of samples, you can still see that we were able to cluster some of our data by cell types without providing that classification to the algorithm! Great job team!

We can see that we get close clustering of tissues like placenta, heart, and bowel. Our liver samples are kind of everywhere but perhaps using a different perplexity would provide different results.

One interesting thing we can see is that regardless of tissue type, we see some samples are clustering together based on case number - namely case numbers 1, 4 and 5 seem to have some strong underlying expression profiles that connect them across tissue samples. We may also be seeing false relationships so beware!


4.2.0 Uniform Manifold Approximation and Projection

This algorithm for projection is in the same flavour as t-SNE projection but has some differences including:

  1. Increased speed and better preservation of the global structure in your data
  2. A different theoretical foundation used to balance between local and global structure

What does that mean for us? Faster results, and more interpretive results! Otherwise the same issues can apply. The setup is slightly easier with few options to change if you leave the defaults. We can access umap() from the package of the same name. You may also alter the default methods by creating a umap.defaults object. More information on that here

For more tinkering, you can choose to use the uwot package instead where the umap() function has more options that are easily modified.

# Set our seed
set.seed(2024)

# Generate our projection
tissue_umap <- ...(tissue_RNAseq.mx)
Error in ...(tissue_RNAseq.mx): could not find function "..."
# What does the UMAP object look like?
str(tissue_umap)
Error in str(tissue_umap): object 'tissue_umap' not found

4.2.1 Extract information from our UMAP object

Looking at our UMAP object tissue_umap we see it houses the projection parameters used but also some additional variables:

  1. data: holds our original data matrix.
  2. layout: contains the projection coordinates we need for plotting the data.
  3. knn: a weighted k-nearest neighbour graph. This is a graph that connects each observation to its nearest k neighbours. This generates the first topological representation of the data - like an initial sketch.

You may notice again that there is no data that suggests how we arrived at this solution. There are no eigenvectors or values to reverse the projection!

Let’s extract the layout information, combine it with our sample information and project it using ggplot2

# Re-map our projection points with our tissue data

tissue_umap.df <- data.frame(x.coord = tissue_umap$...,
                             y.coord = tissue_umap$...)
Error in data.frame(x.coord = tissue_umap$..., y.coord = tissue_umap$...): object 'tissue_umap' not found
tissue_umap.df <- cbind(tissue_RNAseq.df[,1:6], tissue_umap.df)
Error in cbind(tissue_RNAseq.df[, 1:6], tissue_umap.df): object 'tissue_RNAseq.df' not found
tissue_umap.df <-
  tissue_umap.df %>% 
  # replace NAs with DNW (did not work)
  mutate(viral_load = replace_na(viral_load, replace = "DNW"))
Error in mutate(., viral_load = replace_na(viral_load, replace = "DNW")): object 'tissue_umap.df' not found
head(tissue_umap.df)
Error in head(tissue_umap.df): object 'tissue_umap.df' not found
combo.colours = c(brewer.pal(12, "Paired"), brewer.pal(12, "Set3"), brewer.pal(8, "Set1"))

# 1. Data
ggplot(data = ...) +
  # 2. Aesthetics
  aes(x = x.coord, 
      y = y.coord, 
      colour = tissue) +

  # Themes
  theme_bw() +
  theme(text = element_text(size=20)) +

  # 3. Scaling
  scale_colour_manual(values = combo.colours) +

  # 4. Geoms
  geom_text(aes(label = case_num), size = 10)
Error in eval(expr, envir, enclos): '...' used in an incorrect context

4.2.2 Interpreting our UMAP result

So it looks like without much tinkering we retrieved a fairly nice result. We can see now that liver and kidney are more closely grouped as tissues, while heart samples generally cluster together still. Bowel and jejunum appear spatially grouped and our placenta samples are still close to each other. The clustering we saw with samples 1, 4, and 5 appear to be less severe.

There does appear to be some structure between the lung samples in different case numbers so this might be an avenue to explore next to try and see if there truly is a relationship between these groups.


5.0.0 Class summary

While t-SNE and UMAP produce projections to produce clustered data, you have no route back to understanding their relationships. PCA, on the other hand, is strictly a dimension reduction tool. It does not place or assign datapoints to any groups BUT it is useful to use on large datasets prior to clustering!

Today we took a deep dive into principal component analysis. There are of course different variants of this based on the assumptions you can make about your observations and variables like independent component analysis (ICA, non-Gaussian features) and multiple correspondence analysis (MCA, categorical features). Some additional methods can also be used to store the transformation like a PCA does, notably variational autoencoders (VAE).

Overall we should remember that while PCA can have problems in generating it’s feature extraction, it is deterministic and repeatable. Also, the final results are provided in such a way that new observations could be transformed and projected onto the same principal components. You can also feed these components back into clustering algorithms like k-means to try and identify specific subgroups.

t-SNE and UMAP, on the other hand appear to do a much better job with high-dimensional data. They can preserve local structure and UMAP can also do a fairly good job of preserving global structure. These tools make for great exploratory analysis of your complex datasets. Interpretation of relationships, however, are not mathematically clear like in PCA. These are, after all projections from a higher dimension for our simpler primate brains!


5.1.0 Weekly assignment

This week’s assignment will be found under the current lecture folder under the “assignment” subfolder. It will include an R markdown notebook that you will use to produce the code and answers for this week’s assignment. Please provide answers in markdown or code cells that immediately follow each question section.

Assignment breakdown
Code 50% - Does it follow best practices?
- Does it make good use of available packages?
- Was data prepared properly
Answers and Output 50% - Is output based on the correct dataset?
- Are groupings appropriate
- Are correct titles/axes/legends correct?
- Is interpretation of the graphs correct?

Since coding styles and solutions can differ, students are encouraged to use best practices. Assignments may be rewarded for well-coded or elegant solutions.

You can save and download the markdown notebook in its native format. Submit this file to the the appropriate assignment section by 12:59 pm on the date of our next class: April 18th, 2024.


5.2.0 Acknowledgements

Revision 1.0.0: created and prepared for CSB1021H S LEC0141, 03-2021 by Calvin Mok, Ph.D. Bioinformatician, Education and Outreach, CAGEF.

Revision 1.0.1: edited and prepared for CSB1020H S LEC0141, 03-2022 by Calvin Mok, Ph.D. Bioinformatician, Education and Outreach, CAGEF.

Revision 1.0.2: edited and prepared for CSB1020H S LEC0141, 03-2023 by Calvin Mok, Ph.D. Bioinformatician, Education and Outreach, CAGEF.

Revision 2.0.0: Revised and prepared for CSB1020H S LEC0141, 03-2024 by Calvin Mok, Ph.D. Bioinformatician, Education and Outreach, CAGEF.


The Center for the Analysis of Genome Evolution and Function (CAGEF)

The Centre for the Analysis of Genome Evolution and Function (CAGEF) at the University of Toronto offers comprehensive experimental design, research, and analysis services in microbiome and metagenomic studies, genomics, proteomics, and bioinformatics.

From targeted DNA amplicon sequencing to transcriptomes, whole genomes, and metagenomes, from protein identification to post-translational modification, CAGEF has the tools and knowledge to support your research. Our state-of-the-art facility and experienced research staff provide a broad range of services, including both standard analyses and techniques developed by our team. In particular, we have special expertise in microbial, plant, and environmental systems.

For more information about us and the services we offer, please visit https://www.cagef.utoronto.ca/.

LS0tDQp0aXRsZTogJycNCm91dHB1dDoNCiAgaHRtbF9ub3RlYm9vazogZGVmYXVsdA0KICBwZGZfZG9jdW1lbnQ6IGRlZmF1bHQNCi0tLQ0KDQpgYGB7ciwgZWNobz1GQUxTRX0NCiMgVGhpcyBhbGxvd3MgdGhlIGZpbGUgdG8gYmUgTElWRSBhbmQgcnVuIHdpdGhvdXQgZXJyb3JzIHN0b3BwaW5nIGl0Lg0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVycm9yID0gVFJVRSkNCmBgYA0KDQo6Ojoge2FsaWduPSJjZW50ZXIifQ0KPGltZyBzcmM9Imh0dHBzOi8vZ2l0aHViLmNvbS9jYW1vay9DU0JfQ291cnNlX01hdGVyaWFscy9ibG9iL21haW4vQWR2Vml6L0NBR0VGX3NlcnZpY2VzX3NsaWRlLnBuZz9yYXc9dHJ1ZSIgd2lkdGg9IjcwMCIvPg0KOjo6DQoNCiMgQWR2YW5jZWQgR3JhcGhpY3MgYW5kIERhdGEgVmlzdWFsaXphdGlvbiBpbiBSDQoNCiMgTGVjdHVyZSAwNTogRGltZW5zaW9uIFRyYW5zZm9ybWF0aW9uIExJVkUgSFRNTCBUZXN0DQoNCiMjIDAuMS4wIEFuIG92ZXJ2aWV3IG9mIEFkdmFuY2VkIEdyYXBoaWNzIGFuZCBEYXRhIFZpc3VhbGl6YXRpb24gaW4gUg0KDQoqKiJBZHZhbmNlZCBHcmFwaGljcyBhbmQgRGF0YSBWaXN1YWxpemF0aW9uIGluIFIiKiogaXMgYnJvdWdodCB0byB5b3UgYnkgdGhlIENlbnRyZSBmb3IgdGhlIEFuYWx5c2lzIG9mIEdlbm9tZSBFdm9sdXRpb24gJiBGdW5jdGlvbidzIChDQUdFRikgYmlvaW5mb3JtYXRpY3MgdHJhaW5pbmcgaW5pdGlhdGl2ZS4gVGhpcyBDU0IxMDIxIHdhcyBkZXZlbG9wZWQgdG8gZW5oYW5jZSB0aGUgc2tpbGxzIG9mIHN0dWRlbnRzIHdpdGggYmFzaWMgYmFja2dyb3VuZHMgaW4gUiBieSBmb2N1c2luZyBvbiBhdmFpbGFibGUgcGhpbG9zb3BoaWVzLCBtZXRob2RzLCBhbmQgcGFja2FnZXMgZm9yIHBsb3R0aW5nIHNjaWVudGlmaWMgZGF0YS4gV2hpbGUgdGhlIGRhdGFzZXRzIGFuZCBleGFtcGxlcyB1c2VkIGluIHRoaXMgY291cnNlIHdpbGwgYmUgY2VudHJlZCBvbiBTQVJTLUNvVi0yIGVwaWRlbWlvbG9naWNhbCBhbmQgZ2Vub21pYyBkYXRhLCB0aGUgbGVzc29ucyBsZWFybmVkIGhlcmVpbiB3aWxsIGJlIGJyb2FkbHkgYXBwbGljYWJsZS4NCg0KVGhpcyBsZXNzb24gaXMgdGhlIGZpZnRoIGluIGEgNi1wYXJ0IHNlcmllcy4gVGhlIGFpbSBmb3IgdGhlIGVuZCBvZiB0aGlzIHNlcmllcyBpcyBmb3Igc3R1ZGVudHMgdG8gcmVjb2duaXplIGhvdyB0byBpbXBvcnQsIGZvcm1hdCwgYW5kIGRpc3BsYXkgZGF0YSBiYXNlZCBvbiB0aGVpciBpbnRlbmRlZCBtZXNzYWdlIGFuZCBhdWRpZW5jZS4gVGhlIGZvcm1hdCBhbmQgc3R5bGUgb2YgdGhlc2UgdmlzdWFsaXphdGlvbnMgd2lsbCBoZWxwIHRvIGlkZW50aWZ5IGFuZCBjb252ZXkgdGhlIGtleSBtZXNzYWdlKHMpIGZyb20gdGhlaXIgZXhwZXJpbWVudGFsIGRhdGEuDQoNClRoZSBzdHJ1Y3R1cmUgb2YgdGhlIGNsYXNzIGlzIGEgKipjb2RlLWFsb25nIHN0eWxlKiogaW4gUiBtYXJrZG93biBub3RlYm9va3MuIEF0IHRoZSBzdGFydCBvZiBlYWNoIGxlY3R1cmUsIHNrZWxldG9uIHZlcnNpb25zIG9mIHRoZSBsZWN0dXJlIHdpbGwgYmUgcHJvdmlkZWQgZm9yIHVzZSBvbiB0aGUgW1VuaXZlcnNpdHkgb2YgVG9yb250byBkYXRhdG9vbHMgSHViXShodHRwczovL2RhdGF0b29scy51dG9yb250by5jYSkgc28gc3R1ZGVudHMgY2FuIHByb2dyYW0gYWxvbmcgd2l0aCB0aGUgaW5zdHJ1Y3Rvci4NCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjIDAuMi4wIExlY3R1cmUgb2JqZWN0aXZlcw0KDQpUaGlzIHdlZWsgd2lsbCBmb2N1cyBvbiBleHBsb3JpbmcgdGhlIHJlbGF0aW9uc2hpcHMgd2l0aGluIHlvdXIgZGF0YSBvYnNlcnZhdGlvbnMgY29tcGFyaW5nIGJldHdlZW4gZXhwZXJpbWVudGFsIHNhbXBsZXMgYW5kIGFwcGx5aW5nIGEgc3VpdGUgb2YgY2x1c3RlciwgZGltZW5zaW9ucyByZWR1Y3Rpb24gYW5kIHByb2plY3Rpb24gYWxnb3JpdGhtcy4NCg0KQXQgdGhlIGVuZCBvZiB0aGlzIGxlY3R1cmUgeW91IHdpbGwgaGF2ZSBjb3ZlcmVkIHZpc3VhbGl6YXRpb25zIHJlbGF0ZWQgdG8NCg0KMS4gIENsdXN0ZXJpbmcgd2l0aCBoZWF0bWFwcyBhbmQgZGVuZHJvZ3JhbXMNCjIuICBLLW1lYW5zIGNsdXN0ZXJpbmcNCjMuICBQcmluY2lwYWwgY29tcG9uZW50IGFuYWx5c2lzDQo0LiAgTm9uLWxpbmVhciBwcm9qZWN0aW9ucw0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMgMC4zLjAgQSBsZWdlbmQgZm9yIHRleHQgZm9ybWF0IGluIFIgbWFya2Rvd24NCg0KYGdyZXkgYmFja2dyb3VuZGAgLSBhIHBhY2thZ2UsIGZ1bmN0aW9uLCBjb2RlLCBjb21tYW5kIG9yIGRpcmVjdG9yeS4gQmFja3RpY2tzIGFyZSBhbHNvIHVzZSBmb3IgaW4tbGluZSBjb2RlLlwNCippdGFsaWNzKiAtIGFuIGltcG9ydGFudCB0ZXJtIG9yIGNvbmNlcHQgb3IgYW4gaW5kaXZpZHVhbCBmaWxlIG9yIGZvbGRlclwNCioqYm9sZCoqIC0gaGVhZGluZyBvciBhIHRlcm0gdGhhdCBpcyBiZWluZyBkZWZpbmVkXA0KW2JsdWUgdGV4dF17c3R5bGU9ImNvbG9yOmJsdWUifSAtIG5hbWVkIG9yIHVubmFtZWQgaHlwZXJsaW5rDQoNCmAuLi5gIC0gV2l0aGluIGVhY2ggY29kaW5nIGNlbGwgdGhpcyB3aWxsIGluZGljYXRlIGFuIGFyZWEgb2YgY29kZSB0aGF0IHN0dWRlbnRzIHdpbGwgbmVlZCB0byBjb21wbGV0ZSBmb3IgdGhlIGNvZGUgY2VsbCB0byBydW4gY29ycmVjdGx5Lg0KDQo6Ojogey5hbGVydCAuYWxlcnQtYmxvY2sgLmFsZXJ0LWluZm99DQoqKkJsdWUgYm94OioqIEEga2V5IGNvbmNlcHQgdGhhdCBpcyBiZWluZyBpbnRyb2R1Y2VkDQo6OjoNCg0KOjo6IHsuYWxlcnQgLmFsZXJ0LWJsb2NrIC5hbGVydC13YXJuaW5nfQ0KKipZZWxsb3cgYm94OioqIFJpc2sgb3IgY2F1dGlvbg0KOjo6DQoNCjo6OiB7LmFsZXJ0IC5hbGVydC1ibG9jayAuYWxlcnQtc3VjY2Vzc30NCioqR3JlZW4gYm94ZXM6KiogUmVjb21tZW5kZWQgcmVhZHMgYW5kIHJlc291cmNlcyB0byBsZWFybiBSDQo6OjoNCg0KOjo6IHsuYWxlcnQgLmFsZXJ0LWJsb2NrIC5hbGVydC1kYW5nZXJ9DQoqKlJlZCBib3hlczoqKiBBIGNvbXByZWhlbnNpb24gcXVlc3Rpb24gd2hpY2ggbWF5IG9yIG1heSBub3QgaW52b2x2ZSBhIGNvZGluZyBjZWxsLiBZb3UgdXN1YWxseSBmaW5kIHRoZXNlIGF0IHRoZSBlbmQgb2YgYSBzZWN0aW9uLg0KOjo6DQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyAwLjQuMCBMZWN0dXJlIGFuZCBkYXRhIGZpbGVzIHVzZWQgaW4gdGhpcyBjb3Vyc2UNCg0KIyMjIDAuNC4xIFdlZWtseSBMZWN0dXJlIGFuZCBza2VsZXRvbiBmaWxlcw0KDQpFYWNoIHdlZWssIG5ldyBsZXNzb24gZmlsZXMgd2lsbCBhcHBlYXIgd2l0aGluIHlvdXIgUlN0dWRpbyBmb2xkZXJzLiBXZSBhcmUgcHVsbGluZyBmcm9tIGEgR2l0SHViIHJlcG9zaXRvcnkgdXNpbmcgdGhpcyBbUmVwb3NpdG9yeSBnaXQtcHVsbCBsaW5rXShodHRwczovL3IuZGF0YXRvb2xzLnV0b3JvbnRvLmNhL2h1Yi91c2VyLXJlZGlyZWN0L2dpdC1wdWxsP3JlcG89aHR0cHMlM0ElMkYlMkZnaXRodWIuY29tJTJGY2Ftb2slMkYyMDI0LTAzLUFkdl9HcmFwaGljc19SJnVybHBhdGg9cnN0dWRpbyUyRiZicmFuY2g9bWFpbikuIFNpbXBseSBjbGljayBvbiB0aGUgbGluayBhbmQgaXQgd2lsbCB0YWtlIHlvdSB0byB0aGUgW1VuaXZlcnNpdHkgb2YgVG9yb250byBkYXRhdG9vbHMgSHViXShodHRwczovL2RhdGF0b29scy51dG9yb250by5jYSkuIFlvdSB3aWxsIG5lZWQgdG8gdXNlIHlvdXIgVVRPUmlkIGNyZWRlbnRpYWxzIHRvIGNvbXBsZXRlIHRoZSBsb2dpbiBwcm9jZXNzLiBGcm9tIHRoZXJlIHlvdSB3aWxsIGZpbmQgZWFjaCB3ZWVrJ3MgbGVjdHVyZSBmaWxlcyBpbiB0aGUgZGlyZWN0b3J5IGAvMjAyNC0wMy1BZHZfR3JhcGhpY3NfUi9MZWN0dXJlX1hYYC4gWW91IHdpbGwgZmluZCBhIHBhcnRpYWxseSBjb2RlZCBgc2tlbGV0b24uUm1kYCBmaWxlIGFzIHdlbGwgYXMgYWxsIG9mIHRoZSBkYXRhIGZpbGVzIG5lY2Vzc2FyeSB0byBydW4gdGhlIHdlZWsncyBsZWN0dXJlLg0KDQpBbHRlcm5hdGl2ZWx5LCB5b3UgY2FuIGRvd25sb2FkIHRoZSBSLU1hcmtkb3duIE5vdGVib29rIChgLlJtZGApIGFuZCBkYXRhIGZpbGVzIGZyb20gdGhlIFJTdHVkaW8gc2VydmVyIHRvIHlvdXIgcGVyc29uYWwgY29tcHV0ZXIgaWYgeW91IHdvdWxkIGxpa2UgdG8gcnVuIGluZGVwZW5kZW50bHkgb2YgdGhlIFRvcm9udG8gdG9vbHMuDQoNCiMjIyAwLjQuMiBMaXZlLWNvZGluZyBIVE1MIHBhZ2UNCg0KQSBsaXZlIGxlY3R1cmUgdmVyc2lvbiB3aWxsIGJlIGF2YWlsYWJsZSBhdCBbY2Ftb2suZ2l0aHViLmlvXShodHRwczovL2NhbW9rLmdpdGh1Yi5pby8yMDI0LTAzLkFkdl9HcmFwaGljc19SL2luZGV4Lmh0bWwpIHRoYXQgd2lsbCB1cGRhdGUgYXMgdGhlIGxlY3R1cmUgcHJvZ3Jlc3Nlcy4gQmUgc3VyZSB0byByZWZyZXNoIHRvIHRha2UgYSBsb29rIGlmIHlvdSBnZXQgbG9zdCENCg0KIyMjIDAuNC4zIFBvc3QtbGVjdHVyZSBQREZzDQoNCkFzIG1lbnRpb25lZCBhYm92ZSwgYXQgdGhlIGVuZCBvZiBlYWNoIGxlY3R1cmUgdGhlcmUgd2lsbCBiZSBhIGNvbXBsZXRlZCB2ZXJzaW9uIG9mIHRoZSBsZWN0dXJlIGNvZGUgcmVsZWFzZWQgYXMgYSBQREYgZmlsZSB1bmRlciB0aGUgTW9kdWxlcyBzZWN0aW9uIG9mIFF1ZXJjdXMuDQoNCiMjIyAwLjQuNCBEYXRhIHVzZWQgaW4gdGhpcyBsZXNzb24NCg0KVG9kYXkncyBkYXRhc2V0cyB3aWxsIGZvY3VzIG9uIHRoZSBzbWFsbGVyIGRhdGFzZXRzIHdlIHdvcmtlZCBvbiBpbiBlYXJsaWVyIGxlY3R1cmVzIChuYW1lbHkgb3VyIE9udGFyaW8gcHVibGljIGhlYWx0aCB1bml0IENPVklELTE5IGRlbW9ncmFwaGljcyBkYXRhKSwgYW5kIGEgbmV3IHNldCBvZiBSTkFzZXEgYW5hbHlzaXMgb24gZGlmZmVyZW50IHRpc3N1ZSBzYW1wbGVzIGZyb20gQ09WSUQtMTkgcGF0aWVudHMNCg0KIyMjIDAuNC40LjEgRGF0YXNldCAxOiBwaHVfZGVtb2dyYXBoaWNzX2NlbnN1c19ub3JtLmNzdg0KDQpUaGlzIGlzIGEgY29tYmluYXRpb24gb2YgZGF0YXNldHMgZnJvbSBwcmV2aW91cyBsZWN0dXJlcy4gVGhpcyBpbmNvcnBvcmF0ZXMgUEhVIGRlbW9ncmFwaGljIGRhdGEgd2l0aCBTdGF0c0NhbiBjZW5zdXMgZGF0YSBmcm9tIDIwMTcgdG8gcHJvZHVjZSBhIG5vcm1hbGl6ZWQgZXN0aW1hdGUgb2YgY2FzZXMsIGRlYXRocywgYW5kIGhvc3BpdGFsaXphdGlvbnMgYWNyb3NzIGFnZSBncm91cHMgYW5kIHB1YmxpYyBoZWFsdGggdW5pdHMgaW4gT250YXJpby4NCg0KIyMjIDAuNC40LjIgRGF0YXNldCAyOiBXeWxlcjIwMjBfQUVDX1NBUlNDb1YyXzE3QUFHX3JlYWRjb3VudHMudHN2DQoNClRoaXMgaXMgdGhlIHNhbWUgcmVhZGNvdW50IGRhdGEgd2UgbG9va2VkIGF0IGluIExlY3R1cmUgMDQuIFJOQS1TZXEgcmVhZCBjb3VudCBkYXRhIGdlbmVyYXRlZCBmcm9tIFNBUlMtQ29WMiBpbmZlY3Rpb25zIG9mIEFFQyBjZWxscy4gVXNlZCB0byBjb21wYXJlIHRoZSB0aW1lY291cnNlIG9mIGV4cHJlc3Npb24gKHByby1pbmZsYW1tYXRvcnkpIGNoYW5nZXMgaW4gc2FtcGxlcyB0cmVhdGVkIHdpdGggYW5kIHdpdGhvdXQgSFNQOTAgaW5oaWJpdG9ycy4gUHVibGlzaGVkIGluICppU2NpZW5jZSogZG9pOiA8aHR0cHM6Ly9kb2kub3JnLzEwLjEwMTYvai5pc2NpLjIwMjEuMTAyMTUxPg0KDQojIyMgMC40LjQuMyBEYXRhc2V0IDM6IEdTRTE1MDMxNl9EZXNlcU5vcm1Db3VudHNfZmluYWwudHh0DQoNCkZyb20gRGVzYWkgZXQgYWwuLCAyMDIwIG9uIG1lZFJ4aXYgZG9pOiA8aHR0cHM6Ly9kb2kub3JnLzEwLjExMDEvMjAyMC4wNy4zMC4yMDE2NTI0MT4gdGhpcyBkYXRhc2V0IGhhcyBub3JtYWxpemVkIGV4cHJlc3Npb24gY291bnRzIGZyb20gUk5Bc2VxIGRhdGEuIEl0IGNvdmVycyBtdWx0aXBsZSBzYW1wbGVzIGFuZCB0aXNzdWVzIGZyb20gQ09WSUQtcG9zaXRpdmUgcGF0aWVudHMgd2l0aCBhIGZvY3VzIG9uIGx1bmcgdGlzc3VlLiBUaGUgZXhwcmVzc2lvbiBkYXRhIGhhcyBiZWVuIHVuZmlsdGVyZWQgZm9yIFNBUlMtQ29WLTIgZXhwcmVzc2lvbiBkYXRhIGFzIHdlbGwuDQoNCiMjIyAwLjQuNC40IERhdGFzZXQgNDogMjAyMC4wNy4zMC4yMDE2NTI0MS1zdXBwX3RhYmxlcy54bHN4DQoNCkZyb20gRGVzYWkgZXQgYWwuLCAyMDIwIG9uIG1lZFJ4aXYgZG9pOiA8aHR0cHM6Ly9kb2kub3JnLzEwLjExMDEvMjAyMC4wNy4zMC4yMDE2NTI0MT4gdGhpcyBkYXRhc2V0IGNvbnRhaW5zIHBhdGllbnQgaW5mb3JtYXRpb24gbGlrZSB2aXJhbCBsb2FkIGZyb20gdGlzc3VlcyB0aGF0IHdlcmUgdXNlZCBmb3IgUk5Bc2VxLg0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMgMC41LjAgUGFja2FnZXMgdXNlZCBpbiB0aGlzIGxlc3Nvbg0KDQpgdGlkeXZlcnNlYCB3aGljaCBoYXMgYSBudW1iZXIgb2YgcGFja2FnZXMgaW5jbHVkaW5nIGBkcGx5cmAsIGB0aWR5cmAsIGBzdHJpbmdyYCwgYGZvcmNhdHNgIGFuZCBgZ2dwbG90MmANCg0KYHZpcmlkaXNgIGhlbHBzIHRvIGNyZWF0ZSBjb2xvci1ibGluZCBwYWxldHRlcyBmb3Igb3VyIGRhdGEgdmlzdWFsaXphdGlvbnMNCg0KYFJDb2xvckJyZXdlcmAgaGFzIHNvbWUgaGxlcGZ1bCBwYWxldHRlcyB0aGF0IHdlJ2xsIG5lZWQgdG8gY29sb3VyIG91ciBkYXRhLg0KDQpgZ3Bsb3RzYCB3aWxsIGJlIHVzZWQgdG8gaGVscCBnZW5lcmF0ZSBoZWF0bWFwL2RlbmRyb2dyYW0gdmlzdWFsaXphdGlvbnMuDQoNCmBGYWN0b01pbmVSYCBhbmQgYGZhY3RvZXh0cmFgIHdpbGwgYmUgdXNlZCBmb3IgUENBIGdlbmVyYXRpb24gYW5kIHZpc3VhbGl6YXRpb24NCg0KYFJ0c25lYCBhbmQgYHVtYXBgIGFyZSBwYWNrYWdlcyBpbXBsZW1lbnRpbmcgbm9uLWxpbmVhciBwcm9qZWN0aW9uIGFsZ29yaXRobXMNCg0KYGBge3J9DQojIFNvbWUgcGFja2FnZXMgY2FuIGJlIGluc3RhbGxlZCB2aWEgQmlvY29uZHVjdG9yDQppZiAoIXJlcXVpcmVOYW1lc3BhY2UoIkJpb2NNYW5hZ2VyIiwgcXVpZXRseSA9IFRSVUUpKQ0KICAgIGluc3RhbGwucGFja2FnZXMoIkJpb2NNYW5hZ2VyIikNCg0KQmlvY01hbmFnZXI6Omluc3RhbGwoIkNvbXBsZXhIZWF0bWFwIikNCg0KaW5zdGFsbC5wYWNrYWdlcygiRmFjdG9NaW5lUiIsIGRlcGVuZGVuY2llcyA9IFRSVUUpDQppbnN0YWxsLnBhY2thZ2VzKCJmYWN0b2V4dHJhIiwgZGVwZW5kZW5jaWVzID0gVFJVRSkNCg0KaW5zdGFsbC5wYWNrYWdlcygiUnRzbmUiKQ0KaW5zdGFsbC5wYWNrYWdlcygidW1hcCIpDQoNCiMgLS0tLS0tLS0tLS0tIExlZ2FjeSBpbnN0YWxsYXRpb24gY29kZSB3aGljaCB3ZSBob3BlZnVsbHkgbmV2ZXIgbmVlZCBhZ2FpbiAtLS0tLS0tLS0tIw0KIyBXZSBuZWVkIHRvIHNwZWNpZmljYWxseSBpbnN0YWxsIGEgcGFja2FnZSBjYWxsZWQgcGJrcnRlc3QgZm9yIHRoZSBmYWN0byBwYWNrYWdlcw0KIyBUaGlzIGlzIGR1ZSB0byB1c2luZyBhbiBvbGRlciB2ZXJpb24gb2YgUiANCiMgbGlicmFyeShyZW1vdGVzKQ0KIyBpbnN0YWxsX3ZlcnNpb24oInBia3J0ZXN0IiwgIjAuNS4xIikNCg0KYGBgDQoNCmBgYHtyfQ0KIyBQYWNrYWdlcyB0byBoZWxwIHRpZHkgb3VyIGRhdGENCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShyZWFkeGwpDQpsaWJyYXJ5KG1hZ3JpdHRyKQ0KDQojIFBhY2thZ2VzIGZvciB0aGUgZ3JhcGhpY2FsIGFuYWx5c2lzIHNlY3Rpb24NCmxpYnJhcnkodmlyaWRpcykNCiMgbGlicmFyeShncGxvdHMpICMgaGVhdG1hcDIoKQ0KbGlicmFyeShDb21wbGV4SGVhdG1hcCkgIyBIZWF0bWFwKCkNCmxpYnJhcnkoUkNvbG9yQnJld2VyKQ0KDQojIFVzZWZ1bCBmb3IgUENBIGFuZCBQQ0EgdmlzdWFsaXphdGlvbg0KbGlicmFyeShGYWN0b01pbmVSKQ0KbGlicmFyeShmYWN0b2V4dHJhKQ0KDQojIERhdGEgcHJvamVjdGlvbiBwYWNrYWdlcw0KbGlicmFyeShSdHNuZSkNCmxpYnJhcnkodW1hcCkNCmBgYA0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyAxLjAuMCBEYXRhIGNhdGVnb3JpemF0aW9uLCBkaW1lbnNpb24gcmVkdWN0aW9uLCBhbmQgZGltZW5zaW9uIHRyYW5zZm9ybWF0aW9uDQoNCkxhc3Qgd2VlayB3ZSBsb29rZWQgYXQgYW4gYW5hbHlzaXMgb2YgUk5Bc2VxIGRhdGEgdGhyb3VnaCBhIG51bWJlciBvZiBtZXRob2RzIHN0YXJ0aW5nIHdpdGggYnJvYWQtbGV2ZWwgdm9sY2FubyBwbG90cyBhbmQgbW92aW5nIHRvd2FyZHMgZ2VuZS1sZXZlbCBleHByZXNzaW9uIHZpc3VhbGl6YXRpb25zIHdpdGggZG90cGxvdHMuIEluIGJldHdlZW4gd2Ugc3RvcHBlZCB0byB0YWtlIGEgbG9vayBhdCBoZWF0bWFwcy4gSW4gdGhpcyBpbnN0YW5jZSB3ZSBzaW1wbHkgdXNlZCBoZWF0bWFwcyB0byBjb252ZXkgZXhwcmVzc2lvbiBsZXZlbHMgb2YgbXVsdGlwbGUgZ2VuZXMgYWNyb3NzIG9uZSBvciBtb3JlIHNhbXBsZXMuDQoNCkxvb2tpbmcgbW9yZSBicm9hZGx5LCB3ZSBub3cgd2lzaCB0byBhc2sgcXVlc3Rpb25zIHN1Y2ggYXMgImhvdyAqc2ltaWxhciogYXJlIG91ciBzYW1wbGVzPyIsICJjYW4gb3VyIHNhbXBsZXMgYmUgZ3JvdXBlZCBvciBjYXRlZ29yaXplZCBpbiBzb21lIHdheT8iIGFuZCAiaXMgdGhlcmUgYW4gdW5kZXJseWluZyBzdHJ1Y3R1cmUgb3IgYXJjaGl0ZWN0dXJlIHRvIG91ciBkYXRhPyIgV2UgYnJpZWZseSBkaXNjdXNzZWQgKioqc2NhdHRlcnBsb3QgbWF0cmljZXMqKiogdG8gaGVscCBhbmFseXNlIG91ciBzYW1wbGUgcXVhbGl0eSBhbW9uZ3N0IHJlcGxpY2F0ZSBleHBlcmltZW50cy4NCg0KV2UgY2FuIHN0dWR5IHRoZXNlIHF1ZXN0aW9ucyB1c2luZyBhIG51bWJlciBvZiB0ZWNobmlxdWVzIHRoYXQgcmFuZ2UgZnJvbSBjbHVzdGVyaW5nL2NhdGVnb3JpemF0aW9uIHRvIHByb2plY3Rpb24gb2YgaGlnaC1kaW1lbnNpb25hbCBkYXRhIG9udG8gYSBsb3dlciBzZXQgb2YgZGltZW5zaW9ucyAodXN1YWxseSAyIG9yIDMpLg0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMgMS4xLjAgV2hhdCBraW5kIG9mIGRhdGEgZG8gd2UgY2FyZSBhYm91dD8NCg0KV2UgY2Fubm90IGJlZ2luIG91ciBqb3VybmV5IHVudGlsIHdlIHRhbGsgYWJvdXQgdGhlIG5hdHVyZSBvZiB0aGUgZGF0YSB3ZSBhcmUgaW50ZXJlc3RlZCBpbiBleGFtaW5pbmcuIFVzdWFsbHksIG91ciBkYXRhIHdpbGwgY29uc2lzdCBvZiBtYW55IHNhbXBsZXMgKG9ic2VydmF0aW9ucykgd2l0aCBzb21lIG51bWJlciBvZiBmZWF0dXJlcyBjYXB0dXJlZCBhYm91dCBlYWNoIG9ic2VydmF0aW9uLiBGb3IgZXhhbXBsZSwgd2l0aCBSTkFzZXEgZGF0YSB3ZSBjb3VsZCBjb25zaWRlciB0aGUgbWVhc3VyZW1lbnRzIHdlIGNhcHR1cmUgZm9yICplYWNoKiBnZW5lIGFzIGEgc2VwYXJhdGUgZmVhdHVyZS92YXJpYWJsZS9jb2x1bW4uDQoNCkNvbnZlcnNlbHkgeW91IG1heSBoYXZlIGh1bmRyZWRzIG9yIHRob3VzYW5kcyBvZiBzYW1wbGVzIHlvdSdkIGxpa2UgdG8gY2F0ZWdvcml6ZSBpbiBzb21lIHdheSAob3Igc2hvdyB0aGF0IHlvdSBjYW4gY2F0ZWdvcml6ZSkgd2l0aCBqdXN0IGEgc21hbGxlciBzZXQgb2YgZmVhdHVyZXMuIEZvciBldmVyeSBudXQsIHRoZXJlIGlzIGEgdG9vbCAqb2Ygc29ydHMqIGZvciBjcmFja2luZyBpdCENCg0KV2UnbGwgc3RhcnQgd2l0aCBhIG1vZGlmaWVkIHZlcnNpb24gb2YgdGhlIFBIVSBhZ2UgZ3JvdXAgZGF0YXNldCB0aGF0IHdlJ3ZlIHNlZW4gYmVmb3JlIGZyb20gTGVjdHVyZSAwMiBhbmQgMDMuIFRoZSBkZW1vZ3JhcGhpY3MgZGF0YSBoYXMgYmVlbiBtb2RpZmllZCB0byBpbmNsdWRlIGEgc2V0IG9mIG5vcm1hbGl6ZWQgdmFsdWVzIChpbmRpdmlkdWFscyBwZXIgMTAwaykgdGhhdCB3YXMgY2FsY3VsYXRlZCBiYXNlZCBvbiBTdGF0c0NhbiAyMDE3IGNlbnN1cyBkYXRhLiBNb2RlbGluZyBvZmYgb2YgdGhlc2UgZGF0YSwgdGhlIDIwMjIgc2l6ZXMgZm9yICoqZWFjaCBhZ2UgZ3JvdXAqKiB3ZXJlIGVzdGltYXRlZC4gQ2FzZSwgZGVhdGgsIGFuZCBob3NwaXRhbGl6YXRpb24gY291bnRzIHdlcmUgbm9ybWFsaXplZCBiYXNlZCBvbiB0aGVzZSBzdWJncm91cCBzaXplcyB0byBnZW5lcmF0ZSB2YWx1ZXMgcGVyIDEwMEsgaW5kaXZpZHVhbHMuIFdlJ2xsIGJlIHdvcmtpbmcgd2l0aCB0aGlzIDIwMjIgZGF0YXNldCBtYWlubHkgYmVjYXVzZSBpdCB1dGlsaXplcyBtb3JlIGZpbmUtZ3JhaW5lZCBiaW5uaW5nIG9mIG91ciBhZ2UtZ3JvdXBzIHRoYW4gbW9yZSByZWNlbnQgZGF0YXNldCBmcm9tIHRoZSBPbnRhcmlvIGdvdmVybm1lbnQgYXJjaGl2ZXMuDQoNClRoZSB1cGRhdGVkIGRhdGFzZXQgd2lsbCBiZSBmb3VuZCBpbiBgcGh1X2RlbW9ncmFwaGljc19jZW5zdXNfbm9ybS5jc3ZgIGFuZCB3ZSdsbCB1c2UgaXQgdG8gZ3VpZGUgdXMgdGhyb3VnaCB0d28gc2VjdGlvbnMgb2YgdG9kYXkncyBsZWN0dXJlLg0KDQpMZXQncyBiZWdpbiBieSBsb2FkaW5nIHRoZSBkYXRhIGFuZCB0YWtpbmcgYSBsb29rIGF0IGl0cyBzdHJ1Y3R1cmUuDQoNCmBgYHtyfQ0KIyBJbXBvcnQgdGhlIG5vcm1hbGl6ZWQgZGVtb2dyYXBoaWNzIGRhdGENCmNvdmlkX2RlbW9ncmFwaGljc19ub3JtLmRmID0gcmVhZF9jc3YoLi4uKQ0KDQojIExvb2sgYXQgaXQncyBzdHJ1Y3R1cmUNCnN0cihjb3ZpZF9kZW1vZ3JhcGhpY3Nfbm9ybS5kZiwgZ2l2ZS5hdHRyID0gRkFMU0UpDQpgYGANCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjIDEuMi4wIExvb2tpbmcgZm9yIHRyZW5kcy9ncm91cHMgaW4geW91ciBkYXRhDQoNCkxldCdzIGJlZ2luIGxvb2tpbmcgYXQgb3VyIGBjb3ZpZF9kZW1vZ3JhcGhpY3Nfbm9ybS5kZmAuIFVzaW5nIGEgc2VyaWVzIG9mIGRhdGEgc2V0cywgd2UndmUgY3JlYXRlZCBhIGNvbnNvbGlkYXRlZCBkYXRhc2V0IHdpdGg6DQoNCjEuICBDdW11bGF0aXZlIGNhc2VzLCBkZWF0aHMsIGFuZCBob3NwaXRhbGl6YXRpb25zIGR1ZSB0byBDT1ZJRC0xOSB3aXRoaW4gZWFjaCBhZ2UgZ3JvdXAgcGVyIFBIVS4NCg0KMi4gIFJlcHJlc2VudGF0aW9uIG9mIGVhY2ggYWdlIGdyb3VwIHdpdGhpbiBlYWNoIGRhdGEgY2F0ZWdvcnkgYXMgYSBwZXJjZW50IG9mIHRvdGFsIGluY2lkZW50cyBpbiBlYWNoIFBIVS4NCg0KMy4gIFVzaW5nIDIwMTcgY2Vuc3VzIGRhdGEsIHRoZSBudW1iZXIgb2YgY2FzZXMgcGVyIDEwMCwwMDAgaW5kaXZpZHVhbHMgbm9ybWFsaXplZCBieSBlc3RpbWF0ZWQgcG9wdWxhdGlvbiBzaXplIGZvciBlYWNoIGFnZSBncm91cCB3aXRoaW4gZWFjaCBQSFUuDQoNClRoZSBxdWVzdGlvbiB3ZSB3YW50IHRvIGFuc3dlciBpczogb2YgdGhlIDM0IHB1YmxpYyBoZWFsdGggdW5pdHMsIHdoaWNoIGxvb2sgbW9zdCBzaW1pbGFyIGJhc2VkIG9uIHRoZSAqKipub3JtYWxpemVkIGNhc2UgZGF0YSoqKiBmb3IgZWFjaCBhZ2UgZ3JvdXA/IEluIG9yZGVyIHRvIHZpc3VhbGl6ZSB0aGlzIGRhdGEsIHdlJ2xsIHdhbnQgdG8gY29udmVydCBvdXIgY3VycmVudCBsb25nLWZvcm0gZGF0YSB0aGF0IGxvb2tzIHNvbWV0aGluZyBsaWtlIHRoaXM6DQoNCnwgcHVibGljX2hlYWx0aF91bml0IHwgYWdlX2dyb3VwIHwgdG90YWxfY2FzZXMgfCAuLi4gfCBjYXNlc19wZXJfMTAwayB8IGRlYXRoc19wZXJfMTAwayB8IGhvc3BpdGFsaXphdGlvbnNfcGVyXzEwMGsgfA0KfDotLS0tLS0tLS0tLS0tLS0tLS06fDotLS0tLS0tLS06fDotLS0tLS0tLS0tLTp8Oi0tLTp8Oi0tLS0tLS0tLS0tLS0tOnw6LS0tLS0tLS0tLS0tLS0tOnw6LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLTp8DQp8ICAgICAgIEFsZ29tYSAgICAgICB8ICAwIHRvIDQgICB8ICAgICAxODEgICAgIHwgLi4uIHwgICAgICAzMzAyICAgICAgfCAgICAgICAgMCAgICAgICAgfCAgICAgICAgICAgIDE2NCAgICAgICAgICAgIHwNCnwgICAgICAgQWxnb21hICAgICAgIHwgMTIgdG8gMTkgIHwgICAgIDQxMiAgICAgfCAuLi4gfCAgICAgIDQ0NjQgICAgICB8ICAgICAgICAwICAgICAgICB8ICAgICAgICAgICAgNTQgICAgICAgICAgICAgfA0KfCAgICAgICAgLi4uICAgICAgICAgfCAgICAuLi4gICAgfCAgICAgLi4uICAgICB8IC4uLiB8ICAgICAgLi4uICAgICAgIHwgICAgICAgLi4uICAgICAgIHwgICAgICAgICAgICAuLi4gICAgICAgICAgICB8DQoNCioqKkludG8gc29tZXRoaW5nIGxpa2UgdGhpczoqKioNCg0KfCBwdWJsaWNfaGVhbHRoX3VuaXQgfCBwb3B1bGF0aW9uXzIwMjIgfCBjYXRlZ29yeSB8IDAgdG8gNCB8IDUgdG8gMTEgfCAxMiB0byAxOSB8IDIwIHRvIDM5IHwgNDAgdG8gNTkgfCA2MCB0byA3OSB8IDgwKyAgfA0KfDotLS0tLS0tLS0tLS0tLS0tLS06fDotLS0tLS0tLS0tLS0tLS06fDotLS0tLS0tLTp8Oi0tLS0tLTp8Oi0tLS0tLS06fDotLS0tLS0tLTp8Oi0tLS0tLS0tOnw6LS0tLS0tLS06fDotLS0tLS0tLTp8Oi0tLS06fA0KfCAgICAgICBBbGdvbWEgICAgICAgfCAgICAgMTE3ODQwICAgICAgfCAgY2FzZXMgICB8ICAzMzAyICB8ICA0NDY0ICAgfCAgIDc1NzAgICB8ICAgNDMwMSAgIHwgICA0ODkwICAgfCAgIDIzMzEgICB8IDQxMTYgfA0KfCAgICAgICAgLi4uICAgICAgICAgfCAgICAgICAuLi4gICAgICAgfCAgIC4uLiAgICB8ICAuLi4gICB8ICAgLi4uICAgfCAgIC4uLiAgICB8ICAgLi4uICAgIHwgICAuLi4gICAgfCAgIC4uLiAgICB8IC4uLiAgfA0KDQpXZSBuZWVkIHRvIGRvIHRoZSBmb2xsb3dpbmcgdG8gdGhlIGRhdGFzZXQNCg0KMS4gIFNlbGVjdCBqdXN0IHRoZSB2YXJpYWJsZXMgd2UgYXJlIGludGVyZXN0ZWQgaW4uDQoyLiAgcGl2b3Qgb3V0IHRoZSBhZ2UgZ3JvdXAgZGF0YSBpbnRvIGl0cyBvd24gY29sdW1ucyBzbyB3ZSBoYXZlIDEgb2JzZXJ2YXRpb24gKHJvdykgcGVyIFBIVS4NCjMuICBDb3JyZWN0IHRoZSBwb3NpdGlvbiBvZiB0aGUgYWdlIGdyb3VwcyAoNSB0byAxMSBuZWVkcyB0byBiZSBtb3ZlZCkuDQoNCmBgYHtyfQ0KIyBDcmVhdGUgYSB3aWRlLWZvcm1hdCB2ZXJzaW9uIG9mIG91ciBub3JtYWxpemVkIGRhdGENCmNvdmlkX2RlbW9ncmFwaGljc19ub3JtX3dpZGUuZGYgPC0NCg0KICAjIFN0YXJ0IGJ5IHBhc3NpbmcgYWxvbmcgdGhlIGxvbmctZm9ybSBub3JtYWxpemVkIGRhdGENCiAgY292aWRfZGVtb2dyYXBoaWNzX25vcm0uZGYgJT4lIA0KDQogICMgU2VsZWN0IGZvciBqdXN0IHRoZSBQSFUsIGFnZV9ncm91cCwgcG9wdWxhdGlvbiBzaXplIGFuZCBjYXNlcy9ob3NwL2RlYXRoc19wZXJfMTAwaw0KICBkcGx5cjo6c2VsZWN0KGMoMyw0LDExLDEzOjE1KSkgJT4lIA0KDQogICMgUGl2b3QgdGhlIGRhdGEgdG8gYSBsb25nZXIgZm9ybWF0DQogIHBpdm90X2xvbmdlcihjb2xzID0gLWMoMTozKSwgbmFtZXNfdG8gPSAuLi4sIHZhbHVlc190byA9IC4uLikgJT4lIA0KDQogICMgR2V0IHJpZCBvZiB0aGUgc3VmZml4IG9mICJfcGVyXzEwMGsiDQogIG11dGF0ZShjYXRlZ29yeSA9IC4uLihzdHJpbmcgPSBjYXRlZ29yeSwgcGF0dGVybiA9ICJfcGVyXzEwMGsiKSkgJT4lIA0KDQogICMgUGl2b3QgdGhlIGFnZV9ncm91cC9wZXJfMTAwayBkYXRhIG91dCB3aWRlcg0KICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gLi4uLA0KICAgICAgICAgICAgICB2YWx1ZXNfZnJvbSA9IC4uLg0KICAgICAgICAgICAgICkgJT4lIA0KDQogICMgTW92ZSB0aGUgIjUgdG8gMTEiIGNhdGVnb3J5IHRvIGFmdGVyICIwIHRvIDQiLiBZb3UgY291bGQgdXNlIGEgbG9uZ2VyICJzZWxlY3QiIGNhbGwgdG8gZG8gdGhpcyB0b28hDQogIHJlbG9jYXRlKC4sIGA1IHRvIDExYCwgLmFmdGVyPWAwIHRvIDRgKQ0KDQojIFRha2UgYSBsb29rIGF0IG91ciByZXN1bHRpbmcgZGF0YWZyYW1lDQpoZWFkKGNvdmlkX2RlbW9ncmFwaGljc19ub3JtX3dpZGUuZGYpDQpgYGANCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjIyAxLjIuMSBDYXN0IG91ciBkYXRhIHRvIGEgbWF0cml4IGZvciBoZWF0bWFwcw0KDQpBdCB0aGlzIHBvaW50LCB3ZSB3b3VsZCBub3JtYWxseSBiZSBwcmVwYXJlZCB0byB2aXN1YWxpemUgb3VyIGRhdGEgd2l0aCBgZ2dwbG90YCBidXQgb3VyIGRhdGEgaXMgaW4gKip3aWRlLWZvcm1hdCoqIGFuZCB3ZSdsbCBiZSB1c2luZyBhIHBhY2thZ2UgdGhhdCBwcmVmZXJzIHRvIHdvcmsgd2l0aCAqKm1hdHJpeCoqIGRhdGEuIEluIHRoYXQgY2FzZSwgd2UgbmVlZCB0byBzdHJpcCB0aGUgZGF0YSBkb3duIGZ1cnRoZXIgYmVjYXVzZSBtYXRyaXggZGF0YSBtdXN0IGJlIGFsbCBvZiB0aGUgc2FtZSB0eXBlIGFuZCB3ZSB3YW50IHRvIHdvcmsgd2l0aCBudW1lcmljIGRhdGEhIFdlJ2xsIHVzZSBgYXMubWF0cml4KClgIGZvciBvdXIgY29udmVyc2lvbi4NCg0KMS4gIFdlJ2xsIGZpbHRlciBvdXIgZGF0YXNldCB0byBvbmx5IGluY2x1ZGUgdGhlICIqKmNhc2VzKioiIGRhdGEgd2Ugc2F3IGFib3ZlLCBhbmQgdGhlbiBkcm9wIHRoZSBgY2F0ZWdvcnlgIHZhcmlhYmxlIGZyb20gaXQuDQoyLiAgV2UnbGwgbW92ZSB0aGUgYHB1YmxpY19oZWFsdGhfdW5pdGAgaW5mb3JtYXRpb24gb3ZlciB0byB0aGUgcm93IG5hbWVzIG9mIG91ciBtYXRyaXggc28gd2UgY2FuIHN0aWxsIHRyYWNrIG91ciBkYXRhIHByb3Blcmx5Lg0KMy4gIFdlJ2xsIHN0aWxsIGtlZXAgb3VyIGBwb3B1bGF0aW9uXzIwMjJgIGNvbHVtbiBvZiBkYXRhIGJ1dCB3ZSdsbCBoYXZlIHRvIHJlbWVtYmVyIHRoYXQgaXQncyB0aGVyZS4NCg0KYGBge3J9DQojIE5vdyB3ZSBuZWVkIHRvIG1ha2Ugb3VyIG1hdHJpeCBhbmQgYXNzaWduIHJvdyBuYW1lcy4gDQojIEl0J3Mga2luZCBvZiBhd2t3YXJkIHRvIG5lZWQgdGhpcyByZXF1aXJlbWVudC4NCg0KIyBDYXN0IG91ciBtYXRyaXggYW5kIGRyb3AgdGhlIGZpcnN0IGNvbHVtbg0KY292aWRfZGVtb2dyYXBoaWNzX25vcm0ubXggPC0gY292aWRfZGVtb2dyYXBoaWNzX25vcm1fd2lkZS5kZiAlPiUgDQogIA0KICAjIEZpbHRlciBmb3IganVzdCBjYXNlIGRhdGENCiAgZHBseXI6OmZpbHRlciguLi4pICU+JSANCiAgDQogICMgRHJvcCB0aGUgUEhVIGFuZCBjYXRlZ29yeSBkYXRhDQogIGRwbHlyOjpzZWxlY3QoLWMocHVibGljX2hlYWx0aF91bml0LCBjYXRlZ29yeSkpICU+JQ0KICANCiAgIyBDb252ZXJ0IHRvIGEgbWF0cml4DQogIGFzLm1hdHJpeCgpDQoNCiMgU2V0IHRoZSByb3cgbmFtZXMgdXNpbmcgdGhlIGluZm9ybWF0aW9uIGZyb20gdGhlIGRhdGEgZnJhbWUNCnJvd25hbWVzKGNvdmlkX2RlbW9ncmFwaGljc19ub3JtLm14KSA8LSANCiAgY292aWRfZGVtb2dyYXBoaWNzX25vcm1fd2lkZS5kZiAlPiUgDQogIA0KICAjIFB1bGwgdGhlIFBIVSBuYW1lcw0KICAuLi4gJT4lIA0KICANCiAgIyBDcmVhdGUgYSB1bmlxdWUgdmVjdG9yIG9mIHRoZW0NCiAgLi4uDQoNCiMgVGFrZSBhIHBlZWsgYXQgb3VyIGRhdGENCmhlYWQoY292aWRfZGVtb2dyYXBoaWNzX25vcm0ubXgpDQpgYGANCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjIDEuMy4wIFBsb3QgYW5kIHJlb3JkZXIgb3VyIFBIVXMgYXMgYSBoZWF0bWFwIHdpdGggYEhlYXRtYXAoKWANCg0KRnJvbSB0aGUgYENvbXBsZXhIZWF0bWFwYCBwYWNrYWdlIHdlIGNhbiB1c2UgdGhlIGZ1bmN0aW9uIGBIZWF0bWFwKClgIHRvIGdlbmVyYXRlIGEgaGVhdG1hcCB0aGF0IGNhbiBkbyBhIGxpdHRsZSBtb3JlIHRoYW4gd2hhdCB3ZSB3ZXJlIGRvaW5nIHdpdGggb3VyIGBnZ3Bsb3QyYCB2ZXJzaW9uLiBBIG5pY2UgYXNwZWN0IG9mIHVzaW5nIGBIZWF0bWFwKClgIGlzIHRoYXQgd2UgY2FuIGFzayBpdCB0byByZW9yZGVyIG91ciBzYW1wbGVzIGFsb25nIGJvdGggdGhlIHJvd3MgYW5kIGNvbHVtbnMuIEF0IHRoZSBzYW1lIHRpbWUgd2UgY2FuIHByZXNlbnQgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIGNvbHVtbnMgZWxlbWVudHMgb3Igcm93IGVsZW1lbnRzIGluIHRoZSBmb3JtIG9mIGRlbmRyb2dyYW1zLg0KDQojIyMgMS4zLjEgSG93IGRvIHdlIHJlb3JkZXIgb3VyIGRhdGEgdmlhIGNsdXN0ZXJpbmc/DQoNCkluIGl0cyBjdXJyZW50IGZvcm0sIHdlIGhhdmUgb3VyIG9yZGVyZWQgb3VyIGRhdGEgYWxvbmcgdGhlIGNvbHVtbnMgaW4gYW4gYXNjZW5kaW5nIG9yZGluYWwgYXJyYW5nZW1lbnQuIFdoaWxlIHRoaXMgbWFrZXMgc2Vuc2UgdG8gdXMgbm93LCBpdCBtYXkgbm90IGhlbHAgdG8gdmlzdWFsbHkgaWRlbnRpZnkgdGhlIHN0cm9uZ2VzdCB0cmVuZHMgb3IgZ3JvdXBpbmdzIGluIG91ciBkYXRhLiBDbHVzdGVyaW5nIGF0dGVtcHRzIHRvIGJyaW5nIG9yZGVyIHRvIG91ciBkYXRhIGJ5IGdyb3VwaW5nIGRhdGEgYWNjb3JkaW5nIHRvIHNwZWNpZmljIGFsZ29yaXRobXMuDQoNCk92ZXJhbGwgc2luZ2xlIHBvaW50cyBhcmUgdXN1YWxseSBncm91cGVkIHRvZ2V0aGVyIGFzIG5laWdoYm91cnMgd2l0aCB0aGUgIm5lYXJlc3QiIG5laWdoYm91cnMgZGV0ZXJtaW5lZCBieSBhIG1ldHJpYyBvZiBzb21lIGtpbmQuIFRoZXNlIGNsdXN0ZXJzIGFyZSB0aGVuIGZ1cnRoZXIgZ3JvdXBlZCB1c2luZyB0aGUgc2FtZSBtZXRyaWNzIHVudGlsIHRoZSBlbnRpcmUgc2V0IGlzIHByZXNlbnRlZCBpbiB0aGVzZSBvcmRlcmVkIGdyb3Vwcy4gVGhlc2UgZ3JvdXBpbmdzL3JlbGF0aW9uc2hpcHMgY2FuIGFsc28gYmUgcHJlc2VudGVkIGFzIGRlbmRyb2dyYW1zLiBJbiBvdXIgY3VycmVudCBjYXNlLCB3ZSBhcmVuJ3QgY29uY2VybmVkIHdpdGggZGV0ZXJtaW5pbmcgZ3JvdXBzICpwZXIgc2UqIGJ1dCByYXRoZXIgd2l0aCBqdXN0IGNvbm5lY3RpbmcgdGhlbSBieSB0aGVpciBzaW1pbGFyaXR5IGFuZCB0aGVuIGNyZWF0aW5nIGEgaGllcmFyY2hpY2FsIG9yZGVyLg0KDQpPdXIgZGF0YSBpcyB1c3VhbGx5IGNvZGVkIGluIG4tZGltZW5zaW9uYWwgc3BhY2UsIGRlcGVuZGluZyBvbiB0aGUgbmF0dXJlIG9mIG91ciBkYXRhc2V0LiBGb3IgYGNvdmlkX2RlbW9ncmFwaGljc19ub3JtLm14YCBvdXIgNyBjb2x1bW5zIGFyZSBjb2RlZCBieSBkYXRhIGZyb20gMzQgUEhVcyBtZWFuaW5nIGVhY2ggY29sdW1uIGlzIGNvZGVkIGluIDM0IGRpbWVuc2lvbnMgb3IgMzQgZmVhdHVyZXMuIENvbnZlcnNlbHksIG91ciAzNCByb3dzIHJlcHJlc2VudCBQSFVzIGVhY2ggY29kZWQgYnkgZGF0YSBhY3Jvc3MgNyBhZ2UgZ3JvdXBzIGFuZCB0aGVyZWZvcmUgNyBkaW1lbnNpb25zLg0KDQpJbXBvcnRhbnQgb3IgaGVscGZ1bCBwYXJhbWV0ZXJzIHRvIHJ1biBgSGVhdG1hcCgpYDoNCg0KLSAgIGBtYXRyaXhgOiBvdXIgbWF0cml4IG9iamVjdC4gSXQgKioqbXVzdCBiZSBhIG1hdHJpeCoqKiwgYW5kIG5vdCBhIGRhdGEgZnJhbWUuIEl0IGNvdWxkIGFsc28gYmUgYSB2ZWN0b3IgYnV0IHRoYXQgYmVjb21lcyBhIHNpbmdsZSBjb2x1bW4uDQotICAgYGNvbGA6IGRldGVybWluZXMgdGhlIGNvbG91cnMgdXNlZCBmb3IgdGhlIGltYWdlLg0KLSAgIGBuYW1lYDogdGhlIG5hbWUvdGl0bGUgb2YgdGhlIGhlYXRtYXAgKGFsc28gdXNlIGFzIHRoZSBsZWdlbmQgdGl0bGUgYnkgZGVmYXVsdCkNCi0gICBgcm93XypgIDogc2V0IG91ciByb3cgdGV4dCBwcm9wZXJ0aWVzIGluY2x1ZGluZyB0aXRsZXMgYW5kIGxhYmVsczoNCiAgICAtICAgYHJvd190aXRsZWAsIGByb3dfdGl0bGVfc2lkZWAsIGByb3dfdGl0bGVfZ3BgIChncmFwaGljIHByb3BlcnRpZXMpDQogICAgLSAgIGByb3dfbmFtZXNfc2lkZWAgYHJvd19uYW1lc19ncGANCiAgICAtICAgY29sdW1uIHByb3BlcnRpZXMgY2FuIGFsc28gYmUgY2hhbmdlZCB1c2luZyBgY29sdW1uXypgDQotICAgYGNsdXN0ZXJfcm93c2AgYW5kIGBjbHVzdGVyX2NvbHVtbnNgOiBsb2dpY2FsIHBhcmFtZXRlcnMgdG8gZGV0ZXJtaW5lIGlmIGNsdXN0ZXJpbmcgc2hvdWxkIG9jY3VyIChkZWZhdWx0IGlzIGBUUlVFYCkNCi0gICBgc2hvd19oZWF0bWFwX2xlZ2VuZGA6IHdoZXRoZXIgb3Igbm90IHRvIHNob3cgdGhlIGhlYXRtYXAgbGVnZW5kDQotICAgYGhlYXRtYXBfbGVnZW5kX3BhcmFtYDogU2V0IHRoZSB0aXRsZSBvZiB0aGUgbGVnZW5kIHNwZWNpZmljYWxseSBpcyBgbGlzdCh0aXRsZSA9ICJ4IilgDQotICAgYHNob3dfW3Jvdy9jb2xdX2RlbmRgOiBXaGV0aGVyIG9yIG5vdCB0byBzaG93IGRlbmRvZ3JhbXMgZm9yIHRoZSByb3cvY29sdW1uDQoNClRoZXJlIGFyZSBhY3R1YWxseSAqYSBsb3QqIG9mIG9wdGlvbnMgYnV0IHRoZXNlIHNob3VsZCBoZWxwIHVzIG1ha2UgYSBiYXNpYyBvbmUuIExldCdzIHBsb3Qgb3VyIGRhdGEgd2l0aCBhbmQgd2l0aG91dCBhIGRlbmRyb2dyYW0gdG8gY29tcGFyZS4NCg0KYGBge3J9DQo/SGVhdG1hcA0KYGBgDQoNCmBgYHtyLCBmaWcud2lkdGg9MTIsIGZpZy5oZWlnaHQ9MTB9DQoNCiMgQ3JlYXRlIGEgSGVhdG1hcCBvYmplY3QNCg0KY2FzZXNfaG1hcCA8LSANCiAgIyBTdXBwbHkgb3VyIG1hdHJpeCBtaW51cyB0aGUgcG9wdWxhdGlvbnMgc2l6ZQ0KICBIZWF0bWFwKC4uLiwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIA0KICAgICAgICAgICAgICAgICAgICAgIA0KICAgICAgICAgIGNsdXN0ZXJfcm93cyA9IC4uLiwgY2x1c3Rlcl9jb2x1bW5zID0gLi4uLCAjIERvbid0IGNsdXN0ZXIgb24gZWl0aGVyIHJvd3Mgb3IgY29sdW1ucw0KDQogICAgICAgICAgIyBVc2UgY29sdW1uX3RpdGxlIGFzIHRoZSB0aXRsZSBvZiBvdXIgaGVhdG1hcA0KICAgICAgICAgIGNvbHVtbl90aXRsZSA9ICJIZWF0bWFwIG9mIENPVklELTE5IGNhc2VzIGluIFBIVXMgYnkgYWdlIGdyb3VwOiB1bmNsdXN0ZXJlZCIsDQogICAgICAgICAgICAgICAgICAgICAgDQogICAgICAgICAgIyBSb3RhdGUgdGhlIGxlZ2VuZCBob3Jpem9udGFsbHkgYW5kIGdpdmUgaXQgYSB0aXRsZQ0KICAgICAgICAgIGhlYXRtYXBfbGVnZW5kX3BhcmFtID0gbGlzdCh0aXRsZSA9ICJjYXNlcyBwZXIgMTAwSyBpbmRpdmlkdWFscyIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxlZ2VuZF9kaXJlY3Rpb24gPSAiaG9yaXpvbnRhbCIpLA0KICAgICAgICAgICAgICAgICAgICAgIA0KICAgICAgICAgICMgUm90YXRlIGNvbHVtbiBuYW1lcyB0byBob3Jpem9udGFsDQogICAgICAgICAgY29sdW1uX25hbWVzX3JvdCA9IDAsDQogICAgICAgICAgY29sdW1uX25hbWVzX2NlbnRlciA9IFRSVUUNCiAgICAgICAgICkNCg0KIyBQbG90IHRoZSBoZWF0bWFwIA0KLi4uKGNhc2VzX2htYXAsIA0KICAgICAjIFBsb3QgdGhlIGxlZ2VuZCBvbiB0aGUgYm90dG9tDQogICAgIGhlYXRtYXBfbGVnZW5kX3NpZGUgPSAiYm90dG9tIg0KICAgICkNCmBgYA0KDQpgYGB7ciwgZmlnLndpZHRoPTEyLCBmaWcuaGVpZ2h0PTEwfQ0KDQojIENyZWF0ZSBhIEhlYXRtYXAgb2JqZWN0DQpjYXNlc19obWFwIDwtIA0KICAjIFN1cHBseSBvdXIgbWF0cml4IG1pbnVzIHRoZSBwb3B1bGF0aW9ucyBzaXplDQogIEhlYXRtYXAoY292aWRfZGVtb2dyYXBoaWNzX25vcm0ubXhbLC0xXSwgICAgICAgICANCiAgICAgICAgICAgICAgICAgICAgICANCiAgICAgICAgICBjbHVzdGVyX3Jvd3MgPSAuLi4sIGNsdXN0ZXJfY29sdW1ucyA9IC4uLiwgICAjIENsdXN0ZXIgb24gYm90aCByb3dzIGFuZCBjb2x1bW5zDQogICAgICAgICAgICAgICAgICAgICAgDQogICAgICAgICAgIyBVc2UgY29sdW1uX3RpdGxlIGFzIHRoZSB0aXRsZSBvZiBvdXIgaGVhdG1hcA0KICAgICAgICAgIGNvbHVtbl90aXRsZSA9ICJIZWF0bWFwIG9mIENPVklELTE5IGNhc2VzIGluIFBIVXMgYnkgYWdlIGdyb3VwOiBjbHVzdGVyZWQiLA0KICAgICAgICAgICAgICAgICAgICAgIA0KICAgICAgICAgICMgUm90YXRlIHRoZSBsZWdlbmQgaG9yaXpvbnRhbGx5IGFuZCBnaXZlIGl0IGEgdGl0bGUNCiAgICAgICAgICBoZWF0bWFwX2xlZ2VuZF9wYXJhbSA9IGxpc3QodGl0bGUgPSAiY2FzZXMgcGVyIDEwMEsgaW5kaXZpZHVhbHMiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsZWdlbmRfZGlyZWN0aW9uID0gImhvcml6b250YWwiKSwNCiAgICAgICAgICAgICAgICAgICAgICANCiAgICAgICAgICAjIFJvdGF0ZSBjb2x1bW4gbmFtZXMgdG8gaG9yaXpvbnRhbA0KICAgICAgICAgIGNvbHVtbl9uYW1lc19yb3QgPSAwLA0KICAgICAgICAgIGNvbHVtbl9uYW1lc19jZW50ZXIgPSBUUlVFDQogICAgICAgICApDQoNCiMgUGxvdCB0aGUgaGVhdG1hcCANCmRyYXcoY2FzZXNfaG1hcCwgDQogICAgICMgUGxvdCB0aGUgbGVnZW5kIG9uIHRoZSBib3R0b20NCiAgICAgaGVhdG1hcF9sZWdlbmRfc2lkZSA9ICJib3R0b20iDQogICAgKQ0KYGBgDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyMgMS4zLjIgQ29uY2F0ZW5hdGUgbXVsdGlwbGUgaGVhdG1hcHMgdG9nZXRoZXIgaW50byBhIGBIZWF0bWFwTGlzdGAgb2JqZWN0DQoNCk91ciBoZWF0bWFwIGFib3ZlIGlzIGRyYXduIGZyb20gYSBzaW5nbGUgZGF0YXNldCBjYXRlZ29yeSAtICoqY2FzZXMqKiAtIGJ1dCBpdCBoZWxwcyB1cyB0byBzZWUgdGhhdCB0aGVyZSBhcmUgc29tZSBzdHJvbmcgc2lnbmFscyB0aGF0IGRpZmZlcmVudGlhdGUgYmV0d2VlbiBvdXIgZGlmZmVyZW50IFBIVXMuIE5vdyB0aGlzIGlzIGEgMzR4NyBncmlkIHdoZXJlIHdlIGNhbiBpbnZlc3RpZ2F0ZSBhbGwgb2YgdGhlIGRhdGEgaW4gb3VyIGRhdGFzZXQuIFdoYXQgaGFwcGVucyB3aGVuIHdlIHdhbnQgdG8gcHJvZHVjZSB0aGlzIGtpbmQgb2YgZGF0YSBmcm9tIG91ciBvdGhlciB0d28gbWV0cmljcyBvZiBob3NwaXRhbGl6YXRpb25zIGFuZCBkZWF0aHM/DQoNClRvIGFjY29tcGxpc2ggdGhpcywgd2UgY2FuIHJlcGVhdCBvdXIgc3RlcHMgYW5kIGNyZWF0ZSAzIHNlcGFyYXRlIGBIZWF0bWFwYCBvYmplY3RzICoqKmJ1dCoqKiB3ZSBjYW4gcGxvdCB0aGVtIHRvZ2V0aGVyIGFzIGEgc2luZ2xlICpjb21wbGV4IGhlYXRtYXAqLiBJbiBmYWN0LCByYXRoZXIgdGhhbiBzdG9yZSBtdWx0aXBsZSBoZWF0bWFwIG9iamVjdHMsIHdlJ2xsIGNyZWF0ZSBhIGBIZWF0bWFwTGlzdGAgb2JqZWN0IGJ5IGNvbmNhdGVuYXRpbmcgdG9nZXRoZXIgbXVsdGlwbGUgYEhlYXRtYXBgIG9iamVjdHMgdXNpbmcgYSBgZm9yYCBsb29wLg0KDQpXZSdsbCByZWNyZWF0ZSBvdXIgY29kZSBmcm9tIGFib3ZlIGJ1dCBzaW1wbGlmeSB0aGUgaGVhdG1hcCBjb2RlIGEgbGl0dGxlLiBXaGlsZSB3ZSdyZSBhdCBpdCwgd2UnbGwgYWxzbyBjb252ZXJ0IG91ciBjb2xvdXJzY2hlbWUgdG8gdmlyaWRpcyB3aGlsZSB3ZSdyZSBhdCBpdA0KDQpgYGB7cn0NCiMgQ3JlYXRlIGEgbGlzdCBvZiBoZWF0bWFwIG9iamVjdHMgZnJvbSBvdXIgZGVtb2dyYXBoaWMgZGF0YQ0KaG1fbGlzdCA8LSBOVUxMDQoNCiMgQ3JlYXRlIHNvbWUgcXVpY2sgdmVjdG9ycyB0byBoZWxwIG91cnNlbHZlcyBvdXQNCmNhdGVnb3JpZXMgPC0gLi4uDQoNCiMgU3RvcmUgdGhlIHJvd25hbWVzIHdlIHdhbnQgdG8gdXNlIG9uIG91ciBtYXRyaWNlcw0KbXhfcm93bmFtZXMgPC0gY292aWRfZGVtb2dyYXBoaWNzX25vcm1fd2lkZS5kZiAlPiUgcHVsbChwdWJsaWNfaGVhbHRoX3VuaXQpICU+JSB1bmlxdWUoKQ0KDQojIFVzZSBhIGxvb3AgdG8gc2VwYXJhdGUgdGhlIGRhdGEgYnkgY2F0ZWdvcnkNCmZvciAoaSBpbiBjYXRlZ29yaWVzKSB7DQogIA0KICAjIENyZWF0ZSBhIHRlbXBvcmFyeSBtYXRyaXggb2YgdGhlIHNwZWNpZmljIGNhdGVnb3J5IGRhdGENCiAgdGVtcF9teCA8LSBjb3ZpZF9kZW1vZ3JhcGhpY3Nfbm9ybV93aWRlLmRmICU+JSANCiAgICAgIGRwbHlyOjpmaWx0ZXIoY2F0ZWdvcnkgPT0gaSkgJT4lIA0KICAgICAgZHBseXI6OnNlbGVjdCgtYyhwdWJsaWNfaGVhbHRoX3VuaXQsIGNhdGVnb3J5KSkgJT4lIA0KICAgICAgYXMubWF0cml4KCkNCiAgDQogICMgU2V0IHRoZSByb3duYW1lcw0KICByb3duYW1lcyh0ZW1wX214KSA8LSBteF9yb3duYW1lcw0KICANCiAgIyBDcmVhdGUgYSBIZWF0bWFwIG9iamVjdA0KICBobWFwIDwtIEhlYXRtYXAodGVtcF9teFssLTFdLCAgICMgU3VwcGx5IG91ciBtYXRyaXggbWludXMgdGhlIHBvcHVsYXRpb25zIHNpemUNCiAgICAgICAgICAgICAgICAgIA0KICAgICAgICAgICAgICAgICAgIyBVc2UgYSB2aXJpZGlzIGNvbG91cnNjYWxlIGJyb2tlbiBpbnRvIDEwMCBzZWdtZW50cw0KICAgICAgICAgICAgICAgICAgY29sID0gdmlyaWRpcygxMDApLA0KDQogICAgICAgICAgICAgICAgICAjIFVzZSBjb2x1bW5fdGl0bGUgYXMgdGhlIHRpdGxlIG9mIG91ciBoZWF0bWFwDQogICAgICAgICAgICAgICAgICBjb2x1bW5fdGl0bGUgPSBpLA0KICAgICAgICAgICAgICAgICAgDQogICAgICAgICAgICAgICAgICAjIFJvdGF0ZSB0aGUgbGVnZW5kIGhvcml6b250YWxseSBhbmQgZ2l2ZSBpdCBhIHRpdGxlIGJhc2VkIG9uIGNhdGVnb3J5DQogICAgICAgICAgICAgICAgICBoZWF0bWFwX2xlZ2VuZF9wYXJhbSA9IGxpc3QodGl0bGUgPSBwYXN0ZTAoaSwgIiBwZXIgMTAwSyBpbmRpdmlkdWFscyIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxlZ2VuZF9kaXJlY3Rpb24gPSAiaG9yaXpvbnRhbCIpDQogICAgICAgICAgICAgICAgICkNCiAgDQogICMgQWRkIHRoaXMgdG8gb3VyIGhlYXRtYXAgbGlzdA0KICBobV9saXN0IDwtIC4uLg0KfQ0KDQojIFdoYXQga2luZCBvZiBvYmplY3QgaXMgaG1fbGlzdD8NCmNsYXNzKGhtX2xpc3QpDQpgYGANCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjIyAxLjMuMyBgZHJhdygpYCB5b3VyIGBIZWF0bWFwTGlzdGANCg0KTm93IHRoYXQgd2UgaGF2ZSBvdXIgYEhlYXRtYXBMaXN0YCB3ZSBjYW4gc2ltcGx5IGRyYXcgdGhlIHdob2xlIHRoaW5nIGFuZCBpdCB3aWxsIGF1dG9tYXRpY2FsbHkgY29uY2F0ZW5hdGUgZm9yIHVzLiBPZiBjb3Vyc2UgdGhlcmUgYXJlIG1hbnkgb3B0aW9ucyB3ZSBjYW4gdXNlIHRvIGRpc3BsYXkgdGhlIGxpc3QuIE9uZSB0aGluZyB0byBub3RlIGlzIHRoYXQgdGhlIFsqY2x1c3RlcmluZyBvZiBjb2x1bW5zIGluIGVhY2ggaGVhdG1hcCBpcyBpbmRlcGVuZGVudCpdey51bmRlcmxpbmV9IHdoaWxlIHRoZSBbKmNsdXN0ZXJpbmcgb2Ygcm93cyAoYW5kIHRodXMgb3JkZXIpIGlzIGJhc2VkIG9uIG9ubHkgdGhlIGZpcnN0IGhlYXRtYXAqXXsudW5kZXJsaW5lfSAoYnkgZGVmYXVsdCkuDQoNClVzaW5nIHRoZSBgZHJhdygpYCBtZXRob2QgdG8gZGlzcGxheSB5b3VyIGBIZWF0bWFwTGlzdGAgd2lsbCBhbGxvdyB5b3UgdG8gZnVydGhlciBjdXN0b21pemUgZGV0YWlscyBvZiB0aGUgb3ZlcmFsbCBmaWd1cmUgaW5jbHVkaW5nIHNldHRpbmcgYHJvd190aXRsZWAgYW5kIGBjb2x1bW5fdGl0bGVgIGZvciB0aGUgZW50aXJlIGZpZ3VyZS4NCg0KYGBge3IsIGZpZy53aWR0aD0xOCwgZmlnLmhlaWdodD0xMH0NCg0KZHJhdyguLi4sIA0KICAgICBjb2x1bW5fdGl0bGUgPSAiQ09WSUQtMTkgbWV0cmljcyBicmVha2Rvd24gYnkgYWdlIGdyb3VwIGFuZCBQSFVcbiIsDQogICAgIGNvbHVtbl90aXRsZV9ncCA9IGdwYXIoZm9udHNpemUgPSAyMCkNCiAgICApDQpgYGANCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjIDEuNC4wIEJ1aWxkaW5nIGNvcnJlbGF0aW9uIGhlYXRtYXBzIHdpdGggY29tcGxleCBSTkEtU2VxIGRhdGENCg0KT3VyIGhlYXRtYXAgYWJvdmUgaXMgZHJhd24gZnJvbSBhIHJlbGF0aXZlbHkgc2ltcGxlIGRhdGFzZXQgYnV0IGl0IGhlbHBzIHVzIHRvIHNlZSB0aGF0IHRoZXJlIGFyZSBzb21lIHN0cm9uZyBzaWduYWxzIHRoYXQgZGlmZmVyZW50aWF0ZSBiZXR3ZWVuIG91ciBkaWZmZXJlbnQgUEhVcy4gSXQgYmVjb21lIGNsZWFyIHRoYXQgd2hpbGUgdGhlIGJ1bGsgb2YgY2FzZXMgaW4gZWFjaCBQSFUgaXMgZG9taW5hdGVkIGJ5IHRoZSAyMC0zOSBhZ2Ugc2VnbWVudCwgdGhlIGJ1bGsgb2YgaG9wc2l0YWxpemF0aW9ucyBhbmQgZGVhdGhzIG9yaWdpbmF0ZSBmcm9tIHRoZSA4MCsgc2VnbWVudC4NCg0KT2YgY291cnNlLCB0aGlzIGRhdGEgd2FzIGN1bXVsYXRpdmUgaW5mb3JtYXRpb24gZnJvbSBKYW51YXJ5IDIwMjAgdG8gTWFyY2ggMjAyMi4gV2l0aCB0aGUgYWZ0ZXJtYXRoIG9mIHZhY2NpbmF0aW9ucyBhbmQgZGlmZmVyZW50IHZhcmlhbnRzLCBhIGxvb2sgYXQgdGhlIGN1cnJlbnQgZGVtb2dyYXBoaWNzIG1heSByZXZlYWxzIGEgc2hpZnQgaW4gdGhlc2UgdHJlbmRzLg0KDQpDb25zaWRlcmluZyB0aGF0IHRoZSBoZWF0bWFwIGlzIGEgMzR4NyBncmlkLCBpdCBpcyBlYXNpZXIgdG8gdmlzdWFsaXplIGFuZCBkaXNzZWN0IGFsbCBvZiB0aGUgZGF0YSBpbiBvdXIgZGF0YXNldC4gV2hhdCBoYXBwZW5zLCBob3dldmVyLCB3aGVuIHdlIHdhbnQgdG8gcHJvZHVjZSB0aGlzIGtpbmQgb2YgdmlzdWFsaXphdGlvbiBmcm9tIHNvbWV0aGluZyBtdWNoIGxhcmdlciBvciBtb3JlIGNvbXBsZXg/DQoNClJlY2FsbCBmcm9tIGxhc3QgbGVjdHVyZSB0aGF0IHdlIGludmVzdGlnYXRlZCByZWFkIGNvdW50IGRhdGEgZnJvbSBXeWxlciBldCBhbC4sIDIwMjAgLSBgV3lsZXIyMDIwX0FFQ19TQVJTQ29WMl8xN0FBR19yZWFkY291bnRzLnRzdmAuIFdlIHVzZWQgdGhpcyBkYXRhc2V0IHRvIHByb2R1Y2UgYSBzY2F0dGVycGxvdCBtYXRyaXggdG8gY29tcGFyZSBiZXR3ZWVuIHNvbWUgb2YgdGhlIHJlcGxpY2F0ZSBkYXRhIHdpdGhpbiB0aGUgc2V0LiBBY3Jvc3MgdGhpcyBzZXQgdGhlcmUgYXJlIDM2IHNldHMgb2YgUk5BLVNlcSBkYXRhIHNwYW5uaW5nIDI3LDAxMSBnZW5lcy4NCg0KTGV0J3MgYmVnaW4gYnkgb3BlbmluZyB1cCB0aGUgZGF0YSBmaWxlIGFuZCBnZXR0aW5nIGEgcXVpY2sgcmVtaW5kZXIgb2Ygd2hhdCBpdCBsb29rcyBsaWtlLg0KDQpgYGB7cn0NCiMgUmVhZCBpbiB5b3VyIHJlYWRfY291bnQgZGF0YQ0Kd3lsZXJfcmVhZGNvdW50cy5kZiA8LSByZWFkX3RzdiguLi4pDQoNCnN0cih3eWxlcl9yZWFkY291bnRzLmRmLCBnaXZlLmF0dHIgPSBGQUxTRSkNCmBgYA0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMjIDEuNC4xIFdyYW5nbGUgb3VyIHJlYWRjb3VudCBkYXRhDQoNCkFzIHlvdSBtaWdodCByZWNhbGwgZnJvbSBsYXN0IHdlZWssIHRoZXJlIGlzIHF1aXRlIGEgYml0IG9mIGRhdGEgaW4gdGhpcyBzZXQuIFNvIHRoYXQgd2UgY2FuIG1vcmUgZWFzaWx5IHZpc3VhbGl6ZSBvdXIgZGF0YSwgd2UnbGwgd2FudCB0byB3cmFuZ2xlIGl0IGEgYml0IG1vcmUgYnk6DQoNCjEuICB1cGRhdGluZyB0aGUgY29sdW1uIG5hbWVzIHRvIHJlbW92ZSBzb21lIG9mIHRoZSB1bm5lY2Vzc2FyeSB0aXRsZSBpbmZvcm1hdGlvbi4NCjIuICBmaWx0ZXJpbmcgYnkgcmVhZGNvdW50cyB0byBsaW1pdCBvdXIgZ2VuZXMgdG8gdGhvc2Ugd2l0aCB2YWx1ZXMgYmV0d2VlbiAxMDAwIGFuZCAzMDAwLg0KMy4gIENvbnZlcnQgb3VyIGRhdGFmcmFtZSB0byBhIG1hdHJpeCBhbmQgcmVuYW1lIHRoZSByb3dzIHVzaW5nIHRoZSBnZW5lcy4NCg0KOjo6IHsuYWxlcnQgLmFsZXJ0LWJsb2NrIC5hbGVydC1pbmZvfQ0KKipXaHkgZG8gd2UgZmlsdGVyIG91ciByZWFkIGNvdW50cz8qKiBJbiB0aGlzIGluc3RhbmNlIHdlIGhhdmUgY2hvc2VuIHRvIGZpbHRlciBvdXIgcmVhZCBjb3VudHMuIEluIHNvbWUgY2FzZXMsIHlvdSAqbWlnaHQqIGZpbmQgd2lsZCBzd2luZ3MgaW4gZXhwcmVzc2lvbiwgYW5kIGl0IGlzIHdoYXQgd2UncmUgbG9va2luZyBmb3IgbW9zdCBvZiB0aGUgdGltZS4gRm9yIGluLWNsYXNzIGFuYWx5c2lzLCB0byBzYXZlIGEgYml0IG9uIG1lbW9yeSBhbmQgdGltZSwgd2UgdHJ5IHRvIGxpbWl0IHRoZSByZWFkIGNvdW50cyB0byBhIG5hcnJvdyBzZXQgdG8gcmVkdWNlIG91ciBzZXQgc2l6ZS4gSXQgd2lsbCBhbHNvIGdyZWF0bHkgcmVkdWNlIHRoZSBzaXplIG9mIG91ciBoZWF0bWFwIGZvciB2aWV3aW5nLiBCeSBmaWx0ZXJpbmcsIHdlJ2xsIGdvIGZyb20gMjcsMDExIG9ic2VydmF0aW9ucyB0byAxMDkuDQo6OjoNCg0KYGBge3J9DQp3eWxlcl9yZWFkY291bnRzX2ZpbHRlcmVkLmRmIDwtDQogICAgDQogICAgd3lsZXJfcmVhZGNvdW50cy5kZiAlPiUgDQoNCiAgICAjIFJlbmFtZSB0aGUgY29sdW1ucyBiZSByZW1vdmluZyB0aGUgZmlyc3QgcG9ydGlvbjogQUVDSUlfeHgNCiAgICByZW5hbWVfd2l0aCguLCB+IHN0cl9yZXBsYWNlKHN0cmluZyA9IC54LCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhdHRlcm4gPSByIihcdypfXGQqXykiLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlcGxhY2UgPSAiIikpICU+JSANCg0KICAgICMgZmlsdGVyIG91dCB0aGUgbG93LXJlYWRjb3VudCBkYXRhDQogICAgZmlsdGVyKGlmX2FsbCguY29scyA9IC0xLCAuZm5zID0gfiAueCA+IC4uLiAmIC54IDwgLi4uKSkNCg0KIyBDcmVhdGUgb3VyIGRhdGEgbWF0cml4DQp3eWxlcl9yZWFkY291bnRzLm14IDwtIGFzLm1hdHJpeCh3eWxlcl9yZWFkY291bnRzX2ZpbHRlcmVkLmRmWywgLi4uXSkNCg0KIyBTZXQgdGhlIHJvdyBuYW1lcyB1c2luZyB0aGUgaW5mb3JtYXRpb24gZnJvbSB0aGUgZGF0YSBmcmFtZQ0Kcm93bmFtZXMod3lsZXJfcmVhZGNvdW50cy5teCkgPC0gd3lsZXJfcmVhZGNvdW50c19maWx0ZXJlZC5kZiRnZW5lDQoNCiMgQ2hlY2sgdGhlIGNoYXJhY3RlcmlzdGljcyBvZiBvdXIgbWF0cml4DQpoZWFkKHd5bGVyX3JlYWRjb3VudHMubXgpDQpzdHIod3lsZXJfcmVhZGNvdW50cy5teCkNCmBgYA0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMjIDEuNC4yIFBsb3QgYSBoZWF0bWFwIG9mIHlvdXIgcmVhZGNvdW50IGRhdGENCg0KV2hhdCBpZiB3ZSB3ZXJlIHRvIHByb2R1Y2UgYSBoZWF0bWFwIGRpcmVjdGx5IHdpdGggdGhlIHJhdyBkYXRhPyBJdCB3b3VsZCBiZSBpbXBvc3NpYmxlIHRvIHJlYWQsIGFuZCBpbmNsdWRpbmcgYSBkZW5kcm9ncmFtIHdvdWxkIGFjdHVhbGx5IHRha2UgZm9yZXZlciB0byBwcm9jZXNzIGdpdmVuIHRoZSAyNywwMTEgcm93cyBvZiBkYXRhIHRvIHByb2Nlc3MgYWNyb3NzIHRoZSAzNiBkYXRhc2V0cy4gVGhhdCdzIHdoeSB3ZSdsbCB1c2Ugb3VyIHNtYWxsIHN1YnNldCBvZiBkYXRhIGFzIGFuIGV4YW1wbGUgdG8gYnVpbGQgYSBoZWF0bWFwLg0KDQpMZXQncyBwbG90IHRoZSBkYXRhIG5vdyB3aXRoIGBoZWF0bWFwLjJgIGFuZCBzZWUgaG93IGl0IGxvb2tzIHdpdGggYSBkZW5kcm9ncmFtLg0KDQpgYGB7ciwgZmlnLndpZHRoPTIwLCBmaWcuaGVpZ2h0PTIwfQ0KDQojIENyZWF0ZSBhIEhlYXRtYXAgb2JqZWN0DQp3eWxlcl9obWFwIDwtIEhlYXRtYXAoLi4uLCAgICAgICAgICAgICAgICMgU3VwcGx5IG91ciBtYXRyaXggDQogICAgICAgICAgICAgICAgICAgICAgY2x1c3Rlcl9yb3dzID0gVFJVRSwgY2x1c3Rlcl9jb2x1bW5zID0gVFJVRSwgICAjIENsdXN0ZXIgb24gYm90aCByb3dzIGFuZCBjb2x1bW5zDQogICAgICAgICAgICAgICAgICAgICAgY29sID0gLi4uLA0KICAgICAgICAgICAgICAgICAgICAgIA0KICAgICAgICAgICAgICAgICAgICAgICMgVXNlIGNvbHVtbl90aXRsZSBhcyB0aGUgdGl0bGUgb2Ygb3VyIGhlYXRtYXANCiAgICAgICAgICAgICAgICAgICAgICBjb2x1bW5fdGl0bGUgPSAiSGVhdG1hcCBvZiBXeWxlciBldCBhbC4sIDIwMjAgcmVhZGNvdW50IGRhdGEiLA0KICAgICAgICAgICAgICAgICAgICAgIA0KICAgICAgICAgICAgICAgICAgICAgICMgUm90YXRlIHRoZSBsZWdlbmQgaG9yaXpvbnRhbGx5IGFuZCBnaXZlIGl0IGEgdGl0bGUNCiAgICAgICAgICAgICAgICAgICAgICBoZWF0bWFwX2xlZ2VuZF9wYXJhbSA9IGxpc3QodGl0bGUgPSAicmVhZGNvdW50cyBwZXIgZ2VuZSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxlZ2VuZF9kaXJlY3Rpb24gPSAiaG9yaXpvbnRhbCIpLA0KICAgICAgICAgICAgICAgICAgICAgIA0KICAgICAgICAgICAgICAgICAgICAgKQ0KDQojIFBsb3QgdGhlIGhlYXRtYXAgDQpkcmF3KHd5bGVyX2htYXAsIA0KICAgICAjIFBsb3QgdGhlIGxlZ2VuZCBvbiB0aGUgYm90dG9tDQogICAgIGhlYXRtYXBfbGVnZW5kX3NpZGUgPSAiYm90dG9tIg0KICAgICkNCmBgYA0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMjIDEuNC4zIEdlbmVyYXRlIGEgY29ycmVsYXRpb24gbWF0cml4IG9mIHlvdXIgZGF0YQ0KDQpGcm9tIHRoZSBhYm92ZSBvdXRwdXQsIHdlIGNhbiBzZWUgdGhhdCBvdXIgaGVhdG1hcCBpcyBhbHJlYWR5IHF1aXRlIGhhcmQgdG8gcmVhZCBhbmQgdGhhdCdzIGNvbWluZyBvZmYgb2YgdXNpbmcganVzdCAxMDkgZ2VuZXMhIFdlIGRvLCBob3dldmVyLCBnZXQgc29tZSBzdHJvbmcgdHJlbmRzIHJlZ2FyZGluZyBvdXIgZGF0YXNldHMgLSB3ZSBjYW4gc2VlIGNlcnRhaW4gc2V0cyBvZiByZXBsaWNhdGVzIGFyZSBncm91cGVkIHRvZ2V0aGVyIGluIHRoZSBkYXRhIGJ1dCBub3QgYWxsIG9mIHRoZW0uIFRoaXMgbWlnaHQgYmUgY2xlYXJlciBpZiB3ZSB3ZXJlIHRvIGZhY3RvciBpbiBhbGwgb2YgdGhlIGRhdGFwb2ludHMgb3IganVzdCB0aGUgcmVsZXZhbnQgZ2VuZXMgdGhhdCBkZWZpbmUgdGhlIGRpZmZlcmVuY2VzIGJldHdlZW4gZGF0YXNldHMuIFdlJ2xsIGRpc2N1c3MgdGhlIHNlY29uZCBwYXJ0IG9mIHRoYXQgdGhvdWdodCBsYXRlciBpbiB0aGlzIGxlY3R1cmUuDQoNClJlY2FsbCB0aGF0IHdoYXQgd2UncmUgcmVhbGx5IGludGVyZXN0ZWQgaW4sIHJlZ2FyZGluZyB0aGVzZSByZWFkY291bnQgZGF0YSwgaXMgdG8gYXNrIGp1c3QgaG93IHNpbWlsYXIgdGhlIGV4cGVyaW1lbnRzIGFyZSBiZXR3ZWVuIGVhY2ggb3RoZXIuIFdoZXJlYXMgd2Ugd2VyZSBhIGxpdHRsZSBsaW1pdGVkIGJ5IHRoZSBzcGFjZSBuZWVkZWQgdG8gcHJvZHVjZSB0aGUgc2NhdHRlcnBsb3QgbWF0cml4LCB3ZSB3ZXJlIGFibGUgdG8gcHJvZHVjZSBjb3JyZWxhdGlvbiB2YWx1ZXMgYmV0d2VlbiBleHBlcmltZW50cyBvbiBhIHNtYWxsIHNjYWxlLiBXZSBjYW4gZXh0ZW5kIHRoYXQgdmlzdWFsaXphdGlvbiBmb3J3YXJkIHRvIGNvbXBhcmUgYmV0d2VlbiAqYWxsKiBkYXRhc2V0cyBhbmQgdGhlbiB2aXN1YWxseSBzdW1tYXJpemUgdGhlIGRhdGEgYXMgYSBoZWF0bWFwLg0KDQpUaGUgcG9ydGlvbiBvZiB0aGUgc2NhdHRlcnBsb3QgbWF0cml4IHdlJ2xsIGV4dGVuZCBpcyB0aGUgY29ycmVsYXRpb24gdmFsdWVzLiBXaGVyZWFzIHRoZSBgZ2dwYWlycygpYCBmdW5jdGlvbiB1c2VzIGEgUGVhcnNvbiBjb3JyZWxhdGlvbiwgd2UgY2FuIGNob29zZSBiZXR3ZWVuIFBlYXJzb24gb3IgU3BlYXJtYW4gY29ycmVsYXRpb24uIFNvbWV0aW1lcyB5b3UgbWF5IHdpc2ggdG8gY29tcGFyZSBib3RoIHNpbmNlIFBlYXJzb24gZXhhbWluZXMgbGluZWFyIHJlbGF0aW9uc2hpcHMgd2hlcmVhcyBTcGVhcm1hbiB1c2VzIGEgcmFua2luZyBtZXRob2QgdG8gY29tcGFyZSB2YXJpYWJsZXMgZm9yIG1vbm90b25pYyByZWxhdGlvbnNoaXBzLg0KDQpUbyBnZW5lcmF0ZSBvdXIgbWF0cml4IHdlJ2xsIGVtcGxveSBhIGNvdXBsZSBvZiBhZGRpdGlvbmFsIGZ1bmN0aW9uczoNCg0KLSAgIGBleHBhbmQuZ3JpZCgpYDogd2UgY2FuIHVzZSB0aGlzIHRvIGJ1aWxkIGEgbWF0cml4IG9mIHBhaXItd2lzZSBjb21iaW5hdGlvbiB2YWx1ZXMuIFdlJ2xsIHVzZSB0aGVzZSB0byBnZW5lcmF0ZSB0aGUgcGFpci13aXNlIGNvbHVtbiBjb21iaW5hdGlvbnMgd2Ugd2FudCB0byBjb21wYXJlIHdpdGggYGNvcigpYC4NCg0KLSAgIGBhcHBseSgpYDogYnkgbm93IHlvdSBzaG91bGQgYmUgZmFtaWxpYXIgd2l0aCB0aGlzIGZ1bmN0aW9uLiBXZSdsbCB1c2UgaXQgdG8gaXRlcmF0ZSB0aHJvdWdoIG91ciBwYWlyLXdpc2UgY29sdW1uIGNvbWJpbmF0aW9ucyBhbmQgc2VuZCBlYWNoIG9mIHRob3NlIHRvLi4uDQoNCi0gICBgY29yKClgOiB0aGlzIHdpbGwgcmV0dXJuIHRoZSBjb3JyZWxhdGlvbiBjby1lZmZpY2llbnQgYmFzZWQgb24gdGhlIGBtZXRob2RgIHdlIGNob29zZSAocGVhcnNvbiwga2VuZGFsbCwgc3BlYXJtYW4pDQoNCmBgYHtyfQ0KIyBFeGFtcGxlIG9mIGhvdyB5b3UgY2FuIHVzZSBleHBhbmQuZ3JpZCB0byBtYWtlIHBhaXJ3aXNlIGNvbWJpbmF0aW9uIGJldHdlZW4gdHdvIHNldHMgb2YgZGF0YS4NCmV4cGFuZC5ncmlkKGMoLi4uKSxjKC4uLikpDQpgYGANCg0KYGBge3J9DQojIEZpcnN0IHdlIG5lZWQgdG8gZml4IHRoZSBjb2x1bW4gbmFtZXMgZnJvbSBvdXIgZGF0YQ0Kd3lsZXJfcmVhZGNvdW50c19vbmx5LmRmIDwtICAgICANCiAgICANCiAgICAjIFVzZSB0aGUgRlVMTCBzZXQgb2YgcmVhZGNvdW50IGRhdGENCiAgICB3eWxlcl9yZWFkY291bnRzLmRmICU+JSANCiAgICAjIFJlbmFtZSB0aGUgY29sdW1ucyBiZSByZW1vdmluZyB0aGUgZmlyc3QgcG9ydGlvbjogQUVDSUlfeHgNCiAgICByZW5hbWVfd2l0aCguLCB+IHN0cl9yZXBsYWNlKHN0cmluZyA9IC54LCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhdHRlcm4gPSByIihcdypfXGQqXykiLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlcGxhY2UgPSAiIikpICU+JSANCiAgICAjIERyb3AgdGhlIGZpcnN0IHR3byBjb2x1bW5zIGFzIHdlbGwNCiAgICBkcGx5cjo6c2VsZWN0KGMoMzozOCkpDQoNCiMgQ3JlYXRlIG91ciBjb3JyZWxhdGlvbiBtYXRyaXgNCnd5bGVyX2Nvci5teCA8LQ0KICAjIFdlJ2xsIGFwcGx5IGEgZnVuY3Rpb24gdG8gZWFjaCByb3cgb2YgdGhlIHJlc3VsdGluZyBncmlkIHBhaXJzDQogIG1hdHJpeChhcHBseShleHBhbmQuZ3JpZChjKC4uLiksYyguLi4pKSwgICAgICAgICAgICAgICAgICAgICAjIGdlbmVyYXRlIHRoZSBwYWlyLXdpc2UgY29tYmluYXRpb25zDQogICAgICAgICAgICAgICANCiAgICAgICAgICAgICAgICMgZXhwbG9yZSBpdCByb3ctYnktcm93DQogICAgICAgICAgICAgICBNQVJHSU4gPSAxLCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIA0KICAgICAgICAgICAgICAgDQogICAgICAgICAgICAgICAjIEFwcGx5IGEgZnVuY3Rpb24gdG8gZWFjaCByb3cgd2hpY2ggZ2VuZXJhdGVzIGEgcGVhcnNvbiBjb3JyZWxhdGlvbiBiZXR3ZWVuDQogICAgICAgICAgICAgICAjIGEgc3BlY2lmaWMgcGFpciBvZiBjb2x1bW5zDQogICAgICAgICAgICAgICBmdW5jdGlvbih4KSBjb3Iod3lsZXJfcmVhZGNvdW50c19vbmx5LmRmWywgeFsxXV0sIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHd5bGVyX3JlYWRjb3VudHNfb25seS5kZlssIHhbMl1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJwZWFyc29uIikgICAgICAgICAgICAgICAjIFVzZSBhIFBlYXJzb24gY29ycmVsYXRpb24NCiAgICAgICAgICAgICAgICksICAjIEVuZCB0aGUgYXBwbHkgZnVuY3Rpb24NCiAgICAgICAgICAgICAgICAgICAgICAgDQogICAgICAgICBucm93ID0gMzYsICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgQ2FzdCBvdXIgcmVzdWx0IGFzIGEgbWF0cml4DQogICAgICAgICBkaW1uYW1lcyA9IGxpc3QoY29sbmFtZXMod3lsZXJfcmVhZGNvdW50c19vbmx5LmRmKSwgICAgICMgbmFtZSB0aGUgcm93cyBhbmQgY29sdW1ucw0KICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG5hbWVzKHd5bGVyX3JlYWRjb3VudHNfb25seS5kZikpDQogICAgICAgICApDQoNCiMgdGFrZSBhIGxvb2sgYXQgdGhlIGZpbmFsIG1hdHJpeA0KaGVhZCh3eWxlcl9jb3IubXgpDQpgYGANCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjIyAxLjQuNCBHZW5lcmF0ZSBhIGhlYXRtYXAgb2YgeW91ciBjb3JyZWxhdGlvbiBtYXRyaXgNCg0KTm93IHRoYXQgd2UndmUgY29tcGxldGVkIHRoZSBjb3JyZWxhdGlvbiBtYXRyaXggYW5kIGl0IGFwcGVhcnMgdG8gYmUgY29ycmVjdCwgd2UgY2FuIGdlbmVyYXRlIHRoZSBoZWF0bWFwIG9mIHRoZSBkYXRhLCBhbGxvd2luZyBpdCB0byBncm91cCBkYXRhIGJhc2VkIG9uIHRoZSBQZWFyc29uIGNvcnJlbGF0aW9uIHZhbHVlcy4NCg0KYGBge3IsIGZpZy53aWR0aD0yMCwgZmlnLmhlaWdodD0yMH0NCg0KIyBDcmVhdGUgYSBIZWF0bWFwIG9iamVjdA0Kd3lsZXJfaG1hcCA8LSANCiAgSGVhdG1hcCh3eWxlcl9jb3IubXgsICAgICAgICAgICAgICAgIyBTdXBwbHkgb3VyIG1hdHJpeCANCiAgICAgICAgICAgICAgICAgICAgICANCiAgICAgICAgICAjIENsdXN0ZXIgb24gYm90aCByb3dzIGFuZCBjb2x1bW5zDQogICAgICAgICAgY2x1c3Rlcl9yb3dzID0gVFJVRSwgY2x1c3Rlcl9jb2x1bW5zID0gVFJVRSwgICANCiAgICAgICAgICANCiAgICAgICAgICBjb2wgPSB2aXJpZGlzKDEwMCksDQogICAgICAgICAgICAgICAgICAgICAgDQogICAgICAgICAgIyBVc2UgY29sdW1uX3RpdGxlIGFzIHRoZSB0aXRsZSBvZiBvdXIgaGVhdG1hcA0KICAgICAgICAgIGNvbHVtbl90aXRsZSA9ICJIZWF0bWFwIG9mIFJOQS1TZXEgUGVhcnNvbiBjb3JyZWxhdGlvbiBvbiByZWFkY291bnRzIiwNCiAgICAgICAgICAgICAgICAgICAgICANCiAgICAgICAgICAjIFJvdGF0ZSB0aGUgbGVnZW5kIGhvcml6b250YWxseSBhbmQgZ2l2ZSBpdCBhIHRpdGxlDQogICAgICAgICAgaGVhdG1hcF9sZWdlbmRfcGFyYW0gPSBsaXN0KHRpdGxlID0gIlBlYXJzb24gc2NvcmUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsZWdlbmRfZGlyZWN0aW9uID0gImhvcml6b250YWwiKSwNCg0KICAgICAgICAgICMgU2V0IHRoZSByb3cvY29sdW1uIGxhYmVsIGZvbnQgc2l6ZQ0KICAgICAgICAgIHJvd19uYW1lc19ncCA9IGdwYXIoZm9udHNpemUgPSAxNiksDQogICAgICAgICAgY29sdW1uX25hbWVzX2dwID0gZ3Bhcihmb250c2l6ZSA9IDE2KQ0KICAgICAgICAgICkNCg0KIyBQbG90IHRoZSBoZWF0bWFwIA0KZHJhdyh3eWxlcl9obWFwLCANCiAgICAgIyBQbG90IHRoZSBsZWdlbmQgb24gdGhlIGJvdHRvbQ0KICAgICBoZWF0bWFwX2xlZ2VuZF9zaWRlID0gImJvdHRvbSINCiAgICApDQpgYGANCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjIDEuNS4wIE1ha2UgYSBjbHVzdGVyIGRlbmRyb2dyYW0gd2l0aCBgaGNsdXN0KClgDQoNCkFzIHlvdSBjYW4gc2VlLCBjbHVzdGVyaW5nIG91ciBkYXRhIChvbiB0aGUgcmlnaHQgbWV0cmljISkgbWFrZXMgYSBiaWcgZGlmZmVyZW5jZSBpbiBob3cgb3VyIGRhdGEgaXMgZGlzcGxheWVkLiBJbiBvdXIgbGFzdCBmZXcgaGVhdG1hcHMsIHdlIGFsbG93ZWQgYEhlYXRtYXAoKWAgdG8gZ2VuZXJhdGUgaXQncyBvd24gY2x1c3RlcmluZy4gV2UgY291bGQgaGF2ZSBzdXBwbGllZCBpdCB3aXRoIGEgc3BlY2lmaWMgZnVuY3Rpb24gdG8gY2FsY3VsYXRlIGRpc3RhbmNlcywgb3IgZXZlbiBhIGRlbmRyb2dyYW0gb2JqZWN0IHRvIG9yZGVyIHRoZSBkYXRhIGFzIHdlbGwuIFVuZGVyIHRoZSBob29kLCB0aGUgZnVuY3Rpb24gaXMgZGVmYXVsdGluZyB0byBldWNsaWRlYW4gZGlzdGFuY2VzIGFuZCBhY3R1YWxseSBwYXNzaW5nIHRoYXQgaW5mb3JtYXRpb24gb24gdG8gdGhlIGBoY2x1c3QoKWAgZnVuY3Rpb24gdG8gcHJvZHVjZSB0aGUgZGVuZHJvZ3JhbXMuDQoNClRoZSBjaG9pY2Ugb2YgYG1ldGhvZGAgZGV0ZXJtaW5lcyBob3cgdGhlIGRhdGEgaXMgY2x1c3RlcmVkLiBUaGUgY2hvaWNlcyBpbmNsdWRlOiB3YXJkLkQsIHdhcmQuRDIsIHNpbmdsZSwgY29tcGxldGUgKGRlZmF1bHQpLCBhdmVyYWdlLCBtY3F1aXR0eSwgbWVkaWFuIGFuZCBjZW50cm9pZC4NCg0KSW4gKmdlbmVyYWwqICoqd2FyZC5EKiosICoqd2FyZC5EMioqIGFuZCAqKmNvbXBsZXRlKiogc3RyYXRlZ2llcyB0cnkgdG8gZmluZCBjb21wYWN0IHNtYWxsICJzcGhlcmljYWwiIGNsdXN0ZXJzLiBUaGUgc2luZ2xlIGxpbmthZ2UgYWRvcHRzIGEgJ2ZyaWVuZHMgb2YgZnJpZW5kcycgY2x1c3RlcmluZyBzdHJhdGVneS4gVGhlIG90aGVyIG1ldGhvZHMgY2FuIGJlIHNhaWQgdG8gYWltIGZvciBzb21ld2hlcmUgaW4gYmV0d2Vlbi4gRm9yIG1vcmUgaW5mb3JtYXRpb24gb24gdGhlc2UsIHlvdSBjYW4gZGlnIGludG8gdGhlIFtgaGNsdXN0KClgIGRvY3VtZW50YXRpb25dKGh0dHBzOi8vc3RhdC5ldGh6LmNoL1ItbWFudWFsL1ItcGF0Y2hlZC9saWJyYXJ5L3N0YXRzL2h0bWwvaGNsdXN0Lmh0bWwpDQoNCldlIGNhbiB0dXJuIHRvIGBoY2x1c3QoKWAgdG8gZ2VuZXJhdGUganVzdCBkZW5kcm9ncmFtcyBmb3IgdXMgYnV0IHByaW9yIHRvIHRoYXQgd2Ugc3RpbGwgbmVlZCB0byByZWZvcm1hdCBvdXIgbWF0cml4IGEgbGl0dGxlIHVzaW5nIHRoZSBgZGlzdCgpYCBmdW5jdGlvbi4NCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjIyAxLjUuMSBDYWxjdWxhdGUgdGhlIGRpc3RhbmNlIGJldHdlZW4gb3VyIHBvaW50cyB3aXRoIGBkaXN0KClgDQoNClJlbWVtYmVyIHdlIHRhbGtlZCBhYm91dCBvdXIgZGF0YSBiZWluZyBpbiBuLWRpbWVuc2lvbmFsIHNwYWNlPyBVc2luZyB0aG9zZSBjb29yZGluYXRlcywgdGhlcmUgYXJlIGEgbnVtYmVyIG9mIHdheXMgdG8gY2FsY3VsYXRlIHRoZSBkaXN0YW5jZSBiZXR3ZWVuIHBvaW50cy4gVGhlIHNpbXBsZXN0IGZvcm0gaXMgdG8gY2FsY3VsYXRlIHRoZSBldWNsaWRlYW4gZGlzdGFuY2UgYmV0d2VlbiBwb2ludHMgdXNpbmcgdGhlIGdlbmVyaWMgZm9ybXVsYToNCg0KJCRkKHAscSkgPSBcc3FydHsocF97MX0tcV97Mn0pXjIgKyAocF97Mn0tcV97Mn0pXjIgKyBcbGRvdHMgKyAocF97bn0tcV97bn0pXjJ9JCQNCg0KYnV0IHRoZXJlIGFyZSBhIG51bWJlciBvZiBvdGhlciBvcHRpb25zIGFzIHdlbGwuIEZvciBpbnN0YW5jZSB5b3UgY2FuIGFsc28gY2hvb3NlIHRoZSBtYXhpbXVtIGRpc3RhbmNlIGJldHdlZW4gdHdvIGNvbXBvbmVudHMgKHNhbWUgZGltZW5zaW9uKS4gVGhlIGBkaXN0KClgIGZ1bmN0aW9uIGNhbiBnZW5lcmF0ZSB0aGVzZSB2YWx1ZXMgZm9yIHlvdSB1c2luZyB0aGUgcGFyYW1ldGVyIGBtZXRob2RgIHRvIGRldGVybWluZSBob3cgdGhlIGRpc3RhbmNlIHdpbGwgYmUgdXNlZC4gWW91ciBvcHRpb25zIGFyZTogZXVjbGlkZWFuLCBtYXhpbXVtLCBtYW5oYXR0YW4sIGNhbmJlcnJhLCBiaW5hcnkgb3IgbWlua293c2tpLg0KDQpUaGUgYGRpc3QoKWAgZnVuY3Rpb24gd2lsbCByZXR1cm4gYSBgZGlzdGAgb2JqZWN0IHdoaWNoIGlzIGEgbG93ZXIgdHJpYW5nbGUgZGlzdGFuY2UgbWF0cml4IGJldHdlZW4gYWxsIG9mIHRoZSByb3dzL29ic2VydmF0aW9ucyB1c2luZyB0aGUgY29sdW1ucyBhcyBjb29yZGluYXRlcy4NCg0KTGV0J3MgcmV0dXJuIHRvIG91ciBQSFUgZGF0YSBpbiBgY292aWRfZGVtb2dyYXBoaWNzX25vcm0ubXhgIHRvIHRyeSBpdCBvdXQgYW5kIHNlZSBob3cgaXQgd29ya3MuDQoNCmBgYHtyfQ0KZGlzdChjb3ZpZF9kZW1vZ3JhcGhpY3Nfbm9ybS5teFssLTFdLCANCiAgICAgbWV0aG9kPSJldWNsaWRlYW4iKSAgJT4lIHN0cigpIyBUaGUgZGVmYXVsdCBtZXRob2QgaXMgZXVjbGlkZWFuDQpgYGANCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjIyAxLjUuMiBDbHVzdGVyIHlvdXIgZGlzdGFuY2UgbWF0cml4DQoNCk5vdyB3ZSBhcmUgcmVhZHkgdG8gY2x1c3RlciBvdXIgZGlzdGFuY2UgbWF0cml4IHVzaW5nIHRoZSBgaGNsdXN0KClgIG1ldGhvZA0KDQpQYXJhbWV0ZXJzIHRoYXQgYXJlIGltcG9ydGFudDoNCg0KLSAgIGBtZXRob2RgOiBhbHJlYWR5IGRlc2NyaWJlZCBhcyB0aGUgbWV0aG9kIHdoaWNoIHdlIHdhbnQgdG8gdXNlIHRvIGNsdXN0ZXIgb3VyIGRhdGEuDQoNCiAgICAtICAgd2FyZC5ELCB3YXJkLkQyLCBzaW5nbGUsIGNvbXBsZXRlIChkZWZhdWx0KSwgYXZlcmFnZSwgbWNxdWl0dHksIG1lZGlhbiBhbmQgY2VudHJvaWQuDQoNCi0gICBga2A6IHRoZSBudW1iZXIgb2YgZ3JvdXBzIHdlIHdvdWxkIGxpa2Ugb3VyIGZpbmFsIGRhdGEgdG8gYmUgc3BsaXQgaW50byAtIHJlcHJlc2VudGVkIGJ5IGNvbG91cnMuDQoNCmBgYHtyfQ0KIyBNYWtlIGEgY2x1c3RlciBvYmplY3QgYW5kIHRha2UgYSBsb29rIGF0IGl0DQpwaHUuaGMgPC0gaGNsdXN0KGRpc3QoY292aWRfZGVtb2dyYXBoaWNzX25vcm0ubXhbLC0xXSksIA0KICAgICAgICAgICAgICAgICBtZXRob2QgPSAuLi4pDQoNCiMgV2hhdCBkb2VzIG91ciBoY2x1c3Qgb2JqZWN0IGNvbnRhaW4/DQpzdHIocGh1LmhjKQ0KYGBgDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyMgMS41LjMgUGxvdCB5b3VyIGNsdXN0ZXIgb2JqZWN0IHVzaW5nIGBmdml6X2RlbmQoKWAgZnJvbSB0aGUgcGFja2FnZSBgZmFjdG9leHRyYWANCg0KTm93IHRoYXQgd2UgaGF2ZSBnZW5lcmF0ZWQgb3VyIGNsdXN0ZXJpbmcgb2JqZWN0LCB5b3UgY2FuIHNlZSB0aGF0IGl0IGhvbGRzIGEgbnVtYmVyIG9mIGZlYXR1cmVzIGluY2x1ZGluZyB0aGUgaGVpZ2h0IChsZW5ndGgpIG9mIG91ciBicmFuY2hlcywgYW4gb3JkZXJpbmcgb2Ygb3VyIFBIVXMgYW5kIHRoZSBvcmRlciBpbiB3aGljaCB0aGV5IG1lcmdlLiBUaGUgYG1lcmdlYCBwcm9jZXNzIGlzIGRlc2NyaWJlZCBpbiBtb3JlIGRldGFpbCBhcyB3ZWxsIHdpdGggdGhlIGBoY2x1c3QoKWAgW2RvY3VtZW50YXRpb25dKGh0dHBzOi8vc3RhdC5ldGh6LmNoL1ItbWFudWFsL1ItcGF0Y2hlZC9saWJyYXJ5L3N0YXRzL2h0bWwvaGNsdXN0Lmh0bWwpDQoNClRvIHNpbXBsaWZ5IHRoZSBwbG90dGluZyBwcm9jZXNzLCB3ZSBjYW4gdXNlIHRoZSBgZnZpel9kZW5kKClgIGZ1bmN0aW9uIHdoaWNoIHdpbGwgcGFyc2UgdGhyb3VnaCB0aGUgYGhjbHVzdGAgb2JqZWN0IHRvIHByb2R1Y2UgYSBwcm9wZXIgZGVuZHJvZ3JhbS4gV2UgY2FuIGV2ZW4gc3BlY2lmeSBzb21lIGdyb3VwaW5nIG9yIGNvbG91cmluZyBzY2hlbWVzIGJhc2VkIG9uIGhvdyBtYW55IGdyb3VwcywgYGtgLCB3ZSBiZWxpZXZlIGFyZSBwcmVzZW50IGluIG91ciBkYXRhLg0KDQpXZSBjYW4gdHJlYXQgdGhlIHJlc3VsdGluZyBwbG90IGxpa2UgYSBnZ3Bsb3QgdG8gdXBkYXRlIHBhcnRpY3VsYXIgZWxlbWVudHMgYXMgd2VsbC4NCg0KYGBge3IsIGZpZy53aWR0aD0yMCwgZmlnLmhlaWdodD0yMH0NCiMgUGxvdCBvdXQgb3VyIGRlbmRyb2dyYW0gd2l0aCBmdml6X2RlbmQNCg0KZnZpel9kZW5kKC4uLiwgICAgICAgICAgICAgICAgICAgICAgICAjIHByb3ZpZGUgb3VyIGhjbHVzdCBvYmplY3QNCiAgICAgICAgICBjZXggPSAxLjUsICAgICAgICAgICAgICAgICAgICAgIyBzZXQgdGhlIHRleHQgc2l6ZSB0byBiZSBsYXJnZXINCiAgICAgICAgICBrID0gLi4uLCAgICAgICAgICAgICAgICAgICAgICAgICAjIEhvdyBtYW55IGNsdXN0ZXJzIGRvIHdlIGV4cGVjdCBpbiBvdXIgZGF0YSANCiAgICAgICAgICBwYWxldHRlID0gIlNldDEiLCAgICAgICAgICAgICAgIyB3aGF0IGNvbG91cnMgd2lsbCB3ZSB1c2UuIE1hbnkgb3B0aW9ucyBpbmNsdWRpbmcgUkJyZXdlciBwYWxldHRlcw0KICAgICAgICAgIGhvcml6ID0gVFJVRSwNCiAgICAgICAgICBsYWJlbHNfdHJhY2tfaGVpZ2h0ID0gMTIwMDAsDQogICAgICAgICApICsNCg0KICAgICMgQnVtcCB1cCB0aGUgdGV4dCBzaXplIGZvciBteSBvbGQgZXllcw0KICAgIHRoZW1lKHRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDIwKSkgDQpgYGANCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMgMi4wLjAgQ2x1c3RlcmluZyBkYXRhIGludG8gZ3JvdXBzDQoNClNvIHdlJ3ZlIHZpc3VhbGl6ZWQgb3VyIGRhdGFzZXQgYXMgYSBkZW5kcm9ncmFtIGFuZCBpdCBnaXZlcyB1cyBhbiBpZGVhIG9mIGhvdyB0aGUgc2FtcGxlcyBtaWdodCBiZSByZWxhdGVkIChpbiBuLWRpbWVuc2lvbiBzcGFjZSkgYmFzZWQgb24gdGhlIGNhc2UgdmFsdWVzIHdlIHByb2R1Y2VkIGZyb20gbm9ybWFsaXphdGlvbi4NCg0KSy1tZWFucyBjbHVzdGVyaW5nIGlzIHVuc3VwZXJ2aXNlZCBsZWFybmluZyBmb3IgdW5jYXRlZ29yaXplZCBkYXRhLiBXZSBkb24ndCByZWFsbHkga25vdyB0aGUgdHJhaW5pbmcgbGFiZWxzIG9mIG91ciBkYXRhIGFuZCBhcmUgbW9yZSBpbnRlcmVzdGVkIGluIHNlZWluZyB3aGljaCBvbmVzIGdyb3VwIHRvZ2V0aGVyLiBIb3cgbWFueSBjbHVzdGVycyBzaG91bGQgd2UgYWltIHRvIGdlbmVyYXRlPyBXZSd2ZSBhbHJlYWR5IHNlZW4gdGhhdCBvdXIgZGF0YSBsaWtlbHkgc3BsaXRzIGludG8gMyBncm91cHMgYmFzZWQgb24gdGhlIGhlYXRtYXBzIGFuZCBgaGNsdXN0KClgIGRhdGEuIFdpbGwgd2UgZ2V0IHRoZSBzYW1lIHRoaW5nIHdpdGggYSBrLW1lYW5zIG1ldGhvZD8NCg0KV2hlbiBpbiBkb3VidCwgeW91IGNhbiBxdWlja2x5IGNvbnN1bHQgdGhlIGBmYWN0b01pbmVyYCBmdW5jdGlvbiBgZnZpel9uYmNsdXN0KClgIHdoaWNoIGNhbiBndWVzcyBob3cgbWFueSBjbHVzdGVycyBhcmUgaWRlYWwgZm9yIHJlcHJlc2VudGluZyB5b3VyIGRhdGEuIE5vdGUgdGhhdCBpdCBtYXkgbm90IHByb2R1Y2UgeW91ciBpZGVhbCBudW1iZXIgb2YgY2x1c3RlcnMgYnV0IGlmIHlvdSdyZSBzdHVjaywgdGhpcyBpcyBhIGdvb2Qgc3RhcnQuDQoNClRoZXJlIGFyZSB0aHJlZSBgbWV0aG9kYCBvcHRpb25zOiB3c3MsIGdhcF9zdGF0LCBhbmQgc2lsaG91ZXR0ZSB3aGljaCBhbGwgYWltIHRvIG1pbmltaXplIHRoZSBudW1iZXIgb2YgY2x1c3RlcnMuDQoNCi0gICBgd3NzYCBlbGJvdyBtZXRob2QgYWltcyB0byBtaW5pbWl6ZSBpbnRyYWNsdXN0ZXIgdmFyaWF0aW9uLiBIb3cgY29tcGFjdCBpcyBvdXIgY2x1c3RlcmluZz8gQSBsb3dlciBXU1MgaXMgYmV0dGVyLg0KDQotICAgYHNpbGhvdWV0dGVgIG1lYXN1cmVzIGhvdyB3ZWxsIGFuIG9iamVjdCBsaWVzIHdpdGhpbiBpdCdzIGNsdXN0ZXIgYnkgY29tcHV0aW5nIHRoZSBhdmVyYWdlIGRpc3RhbmNlIHRvIG90aGVyIG1lbWJlcnMgaW4gaXRzIG93biwgJGEoaSkkIHZzIG90aGVyIGNsdXN0ZXJzICRiKGkpJC4gV2UgZXZhbHVhdGUgJFxmcmFje2IoaSkgLSBhKGkpfXttYXhce2EoaSksIGIoaSl9JCB3aXRoIGhpZ2hlciB2YWx1ZXMgaW5kaWNhdGluZyBiZXR0ZXIgY2x1c3RlcmluZy4NCg0KLSAgIGBnYXBfc3RhdGAgYWxzbyBjb21wYXJlcyB0b3RhbCBpbnRyYS1jbHVzdGVyIHZhcmlhdGlvbiBidXQgYWdhaW5zdCBhIG51bGwgcmVmZXJlbmNlIGRpc3RyaWJ1dGlvbi4gVGhlIG9wdGltYWwgdmFsdWUgYXR0ZW1wdHMgdG8gY2hvb3NlIHRoZSBudW1iZXIgb2YgY2x1c3RlcnMgc3VjaCB0aGF0IHRoZSBzbWFsbGVzdCBga2AgZ2FwIHN0YXRpc3RpYyBpcyB3aXRoaW4gb25lIHN0YW5kYXJkIGRldmlhdGlvbiBvZiB0aGUgZ2FwIGF0IGBrKzFgLg0KDQpgYGB7ciwgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9Nn0NCiMgSG93IG1hbnkgY2x1c3RlcnMgc2hvdWxkIHdlIGdlbmVyYXRlPw0KZnZpel9uYmNsdXN0KHggPSBjb3ZpZF9kZW1vZ3JhcGhpY3Nfbm9ybS5teFssLTFdLCAgICMgUHJvdmlkZSB0aGUgZGF0YXNldA0KICAgICAgICAgICAgIEZVTmNsdXN0ZXIgPSAga21lYW5zLCAgICAgICAgICAgICAgICAgICMgSG93IHdpbGwgeW91IGNsdXN0ZXIgdGhlIGRhdGE/DQogICAgICAgICAgICAgbWV0aG9kPS4uLikgKyAgICAgICAgICAgICAgICAgICAgICAgICMgV2hhdCBtZXRob2Qgd2lsbCB5b3UgdXNlPw0KICANCiAgdGhlbWUodGV4dCA9IGVsZW1lbnRfdGV4dChzaXplPTEwKSkNCmBgYA0KDQpgYGB7cn0NCiMgSG93IG1hbnkgY2x1c3RlcnMgc2hvdWxkIHdlIGdlbmVyYXRlPw0KZnZpel9uYmNsdXN0KHggPSBjb3ZpZF9kZW1vZ3JhcGhpY3Nfbm9ybS5teFssLTFdLCAgICMgUHJvdmlkZSB0aGUgZGF0YXNldA0KICAgICAgICAgICAgIEZVTmNsdXN0ZXIgPSAga21lYW5zLCAgICAgICAgICAgICAgICAgICMgSG93IHdpbGwgeW91IGNsdXN0ZXIgdGhlIGRhdGE/DQogICAgICAgICAgICAgbWV0aG9kPSJzaWxob3VldHRlIikgKyAgICAgICAgICAgICAgICAgIyBXaGF0IG1ldGhvZCB3aWxsIHlvdSB1c2U/DQogIA0KICB0aGVtZSh0ZXh0ID0gZWxlbWVudF90ZXh0KHNpemU9MTApKQ0KYGBgDQoNCmBgYHtyfQ0KIyBIb3cgbWFueSBjbHVzdGVycyBzaG91bGQgd2UgZ2VuZXJhdGU/DQpmdml6X25iY2x1c3QoeCA9IGNvdmlkX2RlbW9ncmFwaGljc19ub3JtLm14WywtMV0sICAgIyBQcm92aWRlIHRoZSBkYXRhc2V0DQogICAgICAgICAgICAgRlVOY2x1c3RlciA9ICBrbWVhbnMsICAgICAgICAgICAgICAgICAgIyBIb3cgd2lsbCB5b3UgY2x1c3RlciB0aGUgZGF0YT8NCiAgICAgICAgICAgICBtZXRob2Q9ImdhcF9zdGF0IikgKyAgICAgICAgICAgICAgICAgICAjIFdoYXQgbWV0aG9kIHdpbGwgeW91IHVzZT8NCiAgDQogIHRoZW1lKHRleHQgPSBlbGVtZW50X3RleHQoc2l6ZT0xMCkpDQpgYGANCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjIDIuMS4wIEdlbmVyYXRlIHlvdXIgY2x1c3RlcnMgdXNpbmcgYGttZWFucygpYA0KDQpTbyBmcm9tIHRocmVlIGRpZmZlcmVudCBhbmFseXNlcyB3ZSBkaWRuJ3QgcmVhbGx5IGdldCBhIGNvbmNlbnN1cyBvbiB0aGUgYmVzdCBudW1iZXIgb2YgY2x1c3RlcnM6DQoNCi0gICB3c3M6IE91ciBlbGJvdyBsZXZlbCBvdXQgYXQgYXJvdW5kIGs9MyBvciBrPTQNCg0KLSAgIHNpbGhvdWV0dGU6IE91ciBiaWdnZXN0IGp1bXAgaXMgYXQgaz0yIHdoaWNoIGlzIGFsc28gb3VyIGJpZ2dlc3QgdmFsdWUNCg0KLSAgIGdhcF9zdGF0OiBUaGUgYmlnZ2VzdCBqdW1wIGluIHdpdGhpbi1jbHVzdGVyIGRpc3RhbmNlIGlzIGF0IGs9MyBhbHRob3VnaCB3ZSBzZWUgYW5vdGhlciBqdW1wIGFnYWluIGF0IGs9NQ0KDQpidXQgaXQgbG9va3MgbGlrZSB0aGUgYW5zd2VyIHJhbmdlcyBiZXR3ZWVuIDIgYW5kIDUgY2x1c3RlcnMuIE5vdGUgdGhhdCBpZiB5b3VyIGRhdGEgY29uc2lzdGVudGx5IHN1Z2dlc3QgayA9IDEsIHRoZW4geW91IHNob3VsZG4ndCBjbHVzdGVyIGF0IGFsbCEgU2luY2Ugb3VyIGRhdGEgYWxyZWFkeSBzdWdnZXN0cyAzIGNsdXN0ZXJzLCBsZXQncyBzdGFydCB3aXRoIHRoYXQuIFdlJ2xsIHVzZSB0aGUgYGttZWFucygpYCBmdW5jdGlvbiB0byBhY2NvbXBsaXNoIHRoaXMuDQoNClNpbWlsYXIgaW4gaWRlYSB0byBgaGNsdXN0KClgLCB0aGUgYGttZWFucygpYCBhbGdvcml0aG0gYXR0ZW1wdHMgdG8gZ2VuZXJhdGUgay1wYXJ0aXRpb25lZCBncm91cHMgZnJvbSB0aGUgZGF0YSBzdXBwbGllZCB3aXRoIGFuIGVtcGhhc2lzIG9uIG1pbmltaXppbmcgdGhlIHN1bSBvZiBzcXVhcmVzIGZyb20gcG9pbnRzIHRvIHRoZSBhc3NpZ25lZCBjbHVzdGVyIGNlbnRlcnMuIFRoaXMgZGlmZmVycyBmcm9tIGBoY2x1c3QoKWAgd2hpY2ggZmluaXNoZXMgYnVpbGRpbmcgdGhlIGVudGlyZSByZWxhdGlvbnNoaXAgdmlhIGRlbmRyb2dyYW0gd2l0aG91dCBhY3R1YWxseSBjaG9vc2luZyAiY2x1c3RlcnMiLg0KDQpXZSdyZSBnb2luZyB0byBhbHNvIHVzZSBgc2V0LnNlZWQoKWAgZm9yIG91ciByYW5kb20gbnVtYmVyIGdlbmVyYXRpb24uIFdlIHRlbmQgdG8gdGhpbmsgb2YgdGhpbmdzIGFzIHJhbmRvbSwgYnV0IGNvbXB1dGF0aW9uYWxseSwgcmFuZG9tbmVzcyBpcyBidWlsdCBvbiBhbGdvcml0aG1zLiBUaGVyZWZvcmUgd2UgY2FuICJyZWNyZWF0ZSIgb3VyIHJhbmRvbW5lc3MgaWYgd2UgdXNlIGEgcHJlLWRldGVybWluZWQgc3RhcnRpbmcgcG9pbnQgYWxzbyBrbm93biBhcyB0aGUgInNlZWQiLg0KDQpUaGUgcGFyYW1ldGVycyB3ZSdyZSBpbnRlcmVzdGVkIGluIHVzaW5nIHdpdGggYGttZWFucygpYCBhcmU6DQoNCi0gICBgeGAgdGhlICpudW1lcmljIG1hdHJpeCogb2Ygb3VyIGRhdGENCg0KLSAgIGBjZW50ZXJzYCBkZXRlcm1pbmVzIHRoZSBudW1iZXIgb2YgY2x1c3RlcnMgd2Ugd2FudCB0byBwcm9kdWNlDQoNCi0gICBgbnN0YXJ0YCBkZWZpbmVzIGhvdyBtYW55IHJhbmRvbWx5IGNob3NlbiBzZXRzIG9mIGsgY2VudHJlcyB5b3UnbGwgdXNlIHRvIHN0YXJ0IHRoZSBhbmFseXNpcy4gQ2hvb3NpbmcgbXVsdGlwbGUgZGlmZmVyZW50IHNldHMgYWxsb3dzIHRoZSBhbGdvcml0aG0gdG8gYXZvaWQgbG9jYWwgbWluaW1hLg0KDQotICAgYGl0ZXIubWF4YCBpcyB0aGUgbWF4IG51bWJlciBvZiBpdGVyYXRpb25zIGFsbG93ZWQgd2hpbGUgdHJ5aW5nIHRvIGNvbnZlcmdlIG9uIHRoZSBiZXN0IG1pbmltdW0gbWV0cmljDQoNCmBgYHtyfQ0KIyBDb21wdXRlIGstbWVhbnMgd2l0aCBrID0gMw0KDQojIFNldCBhIHNlZWQgZm9yIHJlcHJvZHVjaWJpbGl0eS4NCnNldC5zZWVkKDEyMykNCg0KIyBHZW5lcmF0ZSBvdXIgay1tZWFucyBhbmFseXNpcw0KcGh1X2Nhc2VzLmttIDwtIA0KICBrbWVhbnMoLi4uLCAjIFdlJ2xsIHNjYWxlIG91ciBkYXRhIGZvciB0aGlzIChtb3JlIG9uIHRoYXQgbGF0ZXIgdG9vISkNCiAgICAgICAgIGNlbnRlcnMgPSAzLCANCiAgICAgICAgIG5zdGFydCA9IDI1LA0KICAgICAgICAgaXRlci5tYXggPSA1MDApDQpgYGANCg0KYGBge3J9DQojIFdoYXQgaXMgdGhlIHN0cnVjdHVyZSBvZiBvdXIga21lYW5zIG9iamVjdD8NCnN0cihwaHVfY2FzZXMua20pDQpgYGANCg0KYGBge3J9DQojIEstbWVhbnMgY2x1c3RlcnMgc2hvd2luZyB0aGUgZ3JvdXAgb2YgZWFjaCBpbmRpdmlkdWFscw0KcGh1X2Nhc2VzLmttJC4uLg0KYGBgDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyAyLjIuMCBQbG90IHlvdXIgay1tZWFucyByZXN1bHRzIHdpdGggYGZ2aXpfY2x1c3RlcigpYA0KDQpOb3RpY2UgdGhlIHN0cnVjdHVyZSBvZiB0aGlzIG91dHB1dD8gSXQgaW5jbHVkZXMgYGNsdXN0ZXJgIHdoaWNoIGRlbm90ZXMgd2hpY2ggcm93IGJlbG9uZ3MgdG8gZWFjaCBvZiB0aGUgMyBjbHVzdGVycyBjaG9zZW4uIFRoZSBjZW50cmUgb2YgZWFjaCBjbHVzdGVyIGlzIGRlZmluZWQgYnkgYGNlbnRlcnNgIHdoaWNoIGluIHRoaXMgY2FzZSBpcyBhIDN4NyBhcnJheSB3aGVyZSBlYWNoIHJvdyB1c2VzIDcgdmFsdWVzIHRvIGRlZmluZSB0aGVpciBwb3NpdGlvbiB3aXRoaW4gb3VyIDctZGltZW5zaW9uIGRhdGFzZXQuIFdlIGNhbiBzZWUgZWFjaCBjbHVzdGVyJ3MgYHNpemVgIGlzIDE4LCAxMSwgYW5kIDUgUEhVcyByZXNwZWN0aXZlbHkuIE5vdGljZSwgaG93ZXZlciwgdGhhdCBub25lIG9mIHRoZSBvcmlnaW5hbCBjb29yZGluYXRlcyBmb3Igb3VyIGRhdGEgYXJlIHJldGFpbmVkIGluIHRoaXMgb2JqZWN0Lg0KDQpSYXRoZXIgdGhhbiB1c2UgYGdncGxvdGAgZGlyZWN0bHksIHdlJ2xsIHVzZSB0aGUgZ2dwbG90LWNvbXBhdGlibGUgYGZ2aXpfY2x1c3RlcigpYCBmdW5jdGlvbiB0byBoZWxwIHVzIHRyYW5zZm9ybSB0aGUgdmFsdWVzIG9mIG91ciBwb2ludHMgZnJvbSBhIDctZGltZW5zaW9uIGNvb3JkaW5hdGUgc3lzdGVtIHRvIGEgMi1kaW1lbnNpb24gdmlzdWFsIGZvcm1hdCBzdWl0YWJsZSBmb3Igb3VyIHNpbXBsZXIgYnJhaW5zLiBVbmRlciB0aGUgaG9vZCBgZnZpel9jbHVzdGVyKClgIGlzIHBlcmZvcm1pbmcgYSBwcmluY2lwbGUgY29tcG9uZW50IGFuYWx5c2lzIHRvIGdyb3VwIG91ciBgZGF0YWAgYWxvbmcgdGhlIGZpcnN0IHR3byBwcmluY2lwYWwgY29tcG9uZW50cyAobW9yZSBvbiB3aGF0IHRoYXQgbWVhbnMgbGF0ZXIgdG9vISkuDQoNClRoZSBwYXJhbWV0ZXJzIHdlIHNob3VsZCBiZSBjb25jZXJuZWQgd2l0aCBpbiB0aGlzIGZ1bmN0aW9uIGluY2x1ZGU6DQoNCi0gICBgb2JqZWN0YCB0aGUgcGFydGl0aW9uaW5nIG9iamVjdCBmb3Igb3VyIGRhdGEuIEluIHRoZSBjYXNlIG9mIGstbWVhbnMsIGl0IGlzIG91ciBrbWVhbnMgb2JqZWN0Lg0KDQotICAgYGRhdGFgIGlzIHRoZSBvcmlnaW5hbCBkYXRhc2V0IHdlIHVzZWQgdG8gZ2VuZXJhdGUgdGhlIGRhdGEuIFRoaXMgd2lsbCBiZSBuZWNlc3NhcnkgdG8gcGxvdCB0aGUgb3RoZXIgZGF0YSBwb2ludHMuDQoNCi0gICBgZWxsaXBzZS50eXBlYCBkZXRlcm1pbmVzIGhvdyB3ZSdsbCBvdXRsaW5lIG91ciBjbHVzdGVycy4gVGhpcyBjb21lcyB3aXRoIGEgbnVtYmVyIG9mIG9wdGlvbnMgaW5jbHVkaW5nOg0KDQogICAgLSAgIGNvbnZleDogZHJhd3MgYm91bmRhcmllcyBiYXNlZCBvbiB0aGUgb3V0ZXIgcG9pbnRzIGluIHlvdXIgY2x1c3Rlcg0KICAgIC0gICBjb25maWRlbmNlOiBwcm9kdWNlcyBhIGNvbmZpZGVuY2UgZWxsaXBzZSBhcm91bmQgdGhlIGNsdXN0ZXIgY2VudHJlcy4gVGhpcyBjYW4gYmUgZnVydGhlciBhZGp1c3RlZCB1c2luZyB0aGUgcGFyYW1ldGVyIGBlbGxpcHNlLmxldmVsYCB3aG9zZSBkZWZhdWx0IGlzIDAuOTUuDQogICAgLSAgIHQsIG5vcm06IGFzc3VtZSBtdWx0aXZhcmlhdGUgdC1kaXN0cmlidXRpb24gYW5kIG11bHRpdmFyaWF0ZSBub3JtYWwgZGlzdHJpYnV0aW9ucyB0byBwcm9kdWNlIHRoZWlyIGVsbGlwc2VzIHVzaW5nIGBlbGxpcHNlLmxldmVsYCB0byBzZXQgdGhlaXIgcmFkaXVzLg0KICAgIC0gICBldWNsaWQ6IHNldHMgYSBjaXJjbGUgb2YgcmFkaXVzIGBlbGxpcHNlLmxldmVsYCBhcm91bmQgdGhlIGNlbnRyZSBvZiBlYWNoIGNsdXN0ZXIuDQoNCkxldCdzIHNlZSBob3cgb3VyIGRhdGEgaGFzIGJlZW4gcGFydGl0aW9uZWQgYnkgdGhlIGstbWVhbnMgY2x1c3RlcmluZy4NCg0KYGBge3IsIGZpZy53aWR0aD0yMCwgZmlnLmhlaWdodD0xNX0NCg0KIyBQbG90IHRoZSBjbHVzdGVyDQojIFlvdSBjYW4gdHJlYXQgdGhpcyBvYmplY3QgbGlrZSBhIGdncGxvdCBvYmplY3QgYWZ0ZXJ3YXJkcw0KDQpwaHVfa21lYW5zLnBsb3QgPC0NCg0KICBmdml6X2NsdXN0ZXIob2JqZWN0ID0gLi4uLCAjIG91ciBrLW1lYW5zIG9iamVjdA0KICAgICAgICAgICAgICAgZGF0YSA9IC4uLiwgIyBPdXIgb3JpZ2luYWwgZGF0YSBuZWVkZWQgZm9yIFBDQSB0byB2aXN1YWxpemUNCiAgICAgICAgICAgICAgIGVsbGlwc2UudHlwZSA9ICJjb252ZXgiLCANCiAgICAgICAgICAgICAgIGdndGhlbWUgPSB0aGVtZV9idygpLCANCiAgICAgICAgICAgICAgIHJlcGVsPVRSVUUsICMgVHJ5IHRvIGF2b2lkIG92ZXJsYXBwaW5nIHRleHQNCiAgICAgICAgICAgICAgIGxhYmVsc2l6ZSA9IDIwLA0KICAgICAgICAgICAgICAgcG9pbnRzaXplID0gNCwNCiAgICAgICAgICAgICAgIG1haW4gPSAiSy1tZWFucyBjbHVzdGVyaW5nIG9mIFBIVSBieSBub3JtYWxpemVkIGNhc2UgbnVtYmVyIg0KICAgICAgICAgICAgICAgKSArDQoNCiAgIyBTZXQgc29tZSBnZ3Bsb3QgdGhlbWUgaW5mb3JtYXRpb24NCiAgdGhlbWUodGV4dCA9IGVsZW1lbnRfdGV4dChzaXplPTIwKSkgKw0KDQogICMgU2V0IHRoZSBjb2xvdXIgYW5kIGZpbGwgc2NoZW1lIHRvIHZpcmlkaXMNCiAgc2NhbGVfZmlsbF92aXJpZGlzX2QoKSArDQogIHNjYWxlX2NvbG91cl92aXJpZGlzX2QoKQ0KDQojIFByaW50IHRoZSBwbG90IQ0KcGh1X2ttZWFucy5wbG90DQpgYGANCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjIDIuMy4wIENsdXN0ZXJpbmcgeW91ciBSTkEtU2VxIGRhdGENCg0KTGV0J3MgcmV0dXJuIHRvIHRoZSAqV3lsZXIgZXQgYWwuLCAyMDIwKiByZWFkY291bnQgZGF0YSBhbmQgdGFrZSBhIGxvb2sgYXQgaXQgdGhyb3VnaCBhIGRpZmZlcmVudCBsZW5zLiBXaGVyZWFzIGJlZm9yZSB3ZSBoYWQgZ2VuZXJhdGVkIGEgaGVhdG1hcCBvZiB0aGUgZGF0YSBiYXNlZCBvbiBsb29raW5nIGF0IGdlbmVzIGV4cHJlc3NlZCB3aXRoaW4gYSByZWFkY291bnQgcmFuZ2UsIHdlJ2xsIG5vdyB0YWtlIGEgZGlmZmVyZW50IGFwcHJvYWNoLg0KDQpMZXQncyBsb29rIGF0IHRoZSB0b3AgNTAwIHZhcmlhYmxlIGdlbmVzIGluIHRoZSBkYXRhc2V0IHRvIGhlbHAgY2x1c3RlciB0aGVtLiBUbyBhY2NvbXBsaXNoIHRoYXQgd2UnbGwgd2FudCB0byBnZW5lcmF0ZSB0aGUgc3RhbmRhcmQgZGV2aWF0aW9uIGluIHJlYWQgY291bnQgZm9yIGVhY2ggcm93IChnZW5lKS4gV2UnbGwgdXRpbGl6ZSBhIGZldyB2ZXJicyB3ZSBoYXZlbid0IHVzZWQgYmVmb3JlIGluIHRoaXMgY2xhc3M6DQoNCi0gICBgcm93d2lzZSgpYDogaW4gdGhlIHNhbWUgY2xhc3Mgb2YgZnVuY3Rpb25zIGFzIGBncm91cF9ieSgpYCwgdGhpcyB3aWxsIHN1YnRseSBhbHRlciB0aGUgdGliYmxlIHNvIHRoYXQgd2hlbiB3ZSBwZXJmb3JtIG1hdGggb3BlcmF0aW9ucyBvbiBpdCwgdGhlc2Ugd2lsbCBiZSBjb21wbGV0ZWQgb24gYSByb3ctYnktcm93IGJhc2lzLg0KDQotICAgYGNfYWNyb3NzKClgOiB0aGlzIGhlbHBlciB2ZXJzaW9uIG9mIGNvbWJpbmUgKGBjKClgKSBjb21iaW5lcyB2YWx1ZXMgYWNyb3NzIGNvbHVtbnMgd2l0aGluIG91ciB0aWJibGUuDQoNCmBgYHtyfQ0KIyBSZXZpZXcgdGhlIHd5bGVyIHJlYWRjb3VudCBkYXRhDQpoZWFkKHd5bGVyX3JlYWRjb3VudHMuZGYpDQpgYGANCg0KYGBge3J9DQojIFNhdmUgb3VyIHJlc3VsdHMgaW50byBhIG5ldyBkYXRhZnJhbWUNCnd5bGVyX3JlYWRjb3VudHNfZmlsdGVyZWQuZGYgPC0NCiAgDQogICMgU3RhcnQgd2l0aCB0aGUgb3JpZ2luYWwgcmVhZCBjb3VudCBkYXRhDQogIHd5bGVyX3JlYWRjb3VudHMuZGYgJT4lIA0KDQogICMgUmVuYW1lIHRoZSBjb2x1bW5zIGJ5IHJlbW92aW5nIHRoZSBmaXJzdCBwb3J0aW9uOiBBRUNJSV94eA0KICByZW5hbWVfd2l0aCguLCB+IHN0cl9yZXBsYWNlKHN0cmluZyA9IC54LCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXR0ZXJuID0gciIoXHcqX1xkKl8pIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVwbGFjZSA9ICIiKSkgJT4lIA0KDQogICMgZmlsdGVyIG91dCB0aGUgbG93IHJlYWRjb3VudCBkYXRhLiBMb3cgdmFsdWVzIHdpbGwgY3JlYXRlIHdpbGQgdmFyaWFuY2UgZWFzaWx5DQogIGZpbHRlcihpZl9hbGwoLmNvbHMgPSAtMSwgLmZucyA9IH4gLnggPiAxMCkpICU+JSANCg0KICAjIFByZXBhcmUgdG8gZG8gcm93LXdpc2UgY2FsY3VsYXRpb25zDQogIC4uLiAlPiUgDQoNCiAgIyBDYWxjdWxhdGUgdGhlIHN0YW5kYXJkIGRldmlhdGlvbiBhY3Jvc3Mgcm93cw0KICBtdXRhdGUoc3RkZXYgPSBzZCguLi4pKSAlPiUgDQogIA0KICAjIFVuZ3JvdXAgYW5kIHNvcnQgdGhlIGRhdGEgYnkgZGVzY2VuZGluZyB2YWx1ZQ0KICB1bmdyb3VwKCkgJT4lIA0KICBhcnJhbmdlKGRlc2Moc3RkZXYpKSAlPiUgDQogIA0KICAjIFRha2UgdGhlIHRvcCA1MDAgbW9zdCB2YXJpYWJsZSBnZW5lcw0KICBkcGx5cjo6c2xpY2UoMTo1MDApDQpgYGANCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCk5vdyB3ZSdsbCBqdXN0IGNvbnZlcnQgb3VyIGRhdGFmcmFtZSBpbnRvIGEgbWF0cml4IG9mIHZhbHVlcyBzbyB3ZSBjYW4gcGVyZm9ybSBvdXIgdmFyaW91cyBhbmFseXNlcy4NCg0KYGBge3J9DQojIFNhdmUganVzdCB0aGUgUk5BLVNlcSBkYXRhIGludG8gYSBtYXRyaXgNCnd5bGVyX3JlYWRjb3VudHMubXggPC0gYXMubWF0cml4KHd5bGVyX3JlYWRjb3VudHNfZmlsdGVyZWQuZGZbLCAzOjM4XSkNCg0KIyBTZXQgdGhlIHJvdyBuYW1lcyB1c2luZyB0aGUgaW5mb3JtYXRpb24gZnJvbSB0aGUgZGF0YSBmcmFtZQ0Kcm93bmFtZXMod3lsZXJfcmVhZGNvdW50cy5teCkgPC0gd3lsZXJfcmVhZGNvdW50c19maWx0ZXJlZC5kZiRnZW5lDQoNCiMgVGFrZSBhIHF1aWNrIGxvb2sgYXQgdGhlIHJlc3VsdGluZyBtYXRyaXggYW5kIGl0cyBwcm9wZXJ0aWVzDQpoZWFkKHd5bGVyX3JlYWRjb3VudHMubXgpDQpzdHIod3lsZXJfcmVhZGNvdW50cy5teCkNCmBgYA0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMjIDIuMy4xIENyZWF0ZSBhIGhlYXRtYXAgb2YgdGhlIG1vc3QgdmFyaWFibGUgZ2VuZXMNCg0KU2luY2Ugd2UncmUgaGVyZSwgbGV0J3MgdGFrZSBhIHF1aWNrIHN0ZXAgYmFjayBhbmQgbG9vayBhdCB0aGUgaGVhdG1hcCBmcm9tIG91ciBuZXcgZGF0YXNldC4gRG9lcyBpdCByZXZlYWwgYW50aGluZyB0byB1cyBub3cgdGhhdCBpdCBpcyBtb3JlIG51YW5jZWQ/DQoNCmBgYHtyLCBmaWcud2lkdGg9MjAsIGZpZy5oZWlnaHQ9MjB9DQojIENyZWF0ZSBhIEhlYXRtYXAgb2JqZWN0DQp3eWxlcl9obWFwIDwtIA0KICBIZWF0bWFwKC4uLiwgICAgICAgICAgICAgICAjIFN1cHBseSBvdXIgbWF0cml4IA0KICAgICAgICAgICAgICAgICAgICAgIA0KICAgICAgICAgIGNsdXN0ZXJfcm93cyA9IFRSVUUsIGNsdXN0ZXJfY29sdW1ucyA9IFRSVUUsICAgIyBDbHVzdGVyIG9uIGJvdGggcm93cyBhbmQgY29sdW1ucw0KICAgICAgICAgICAgICAgICAgICAgIA0KICAgICAgICAgIGNvbCA9IHZpcmlkaXMoMTAwKSwNCiAgICAgICAgICAgICAgICAgICAgICANCiAgICAgICAgICAjIFVzZSBjb2x1bW5fdGl0bGUgYXMgdGhlIHRpdGxlIG9mIG91ciBoZWF0bWFwDQogICAgICAgICAgY29sdW1uX3RpdGxlID0gIkhlYXRtYXAgb2YgUk5BLVNlcSByZWFkY291bnRzIG9uIHRvcCA1MDAgbW9zdCB2YXJpYWJsZSBnZW5lcyIsDQogICAgICAgICAgICAgICAgICAgICAgDQogICAgICAgICAgIyBSb3RhdGUgdGhlIGxlZ2VuZCBob3Jpem9udGFsbHkgYW5kIGdpdmUgaXQgYSB0aXRsZQ0KICAgICAgICAgIGhlYXRtYXBfbGVnZW5kX3BhcmFtID0gbGlzdCh0aXRsZSA9ICJyZWFkY291bnRzIHBlciBnZW5lIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGVnZW5kX2RpcmVjdGlvbiA9ICJ2ZXJ0aWNhbCIpLA0KICAgICAgICAgICAgICAgICAgICAgIA0KICAgICAgICAgICMgUmVtb3ZlIHRoZSByb3cgbmFtZXMNCiAgICAgICAgICBzaG93X3Jvd19uYW1lcyA9IEZBTFNFLCANCiAgICAgICAgICAgICAgICAgICAgICANCiAgICAgICAgICAjIFNldCB0aGUgZGVuZHJvZ3JhbSBzaXplcw0KICAgICAgICAgIC4uLiA9IHVuaXQoNDAsICJtbSIpLA0KICAgICAgICAgIC4uLiA9IHVuaXQoMjAsICJtbSIpDQogICAgICAgICAgICAgICAgICAgICAgDQogICAgICAgICApDQoNCiMgUGxvdCB0aGUgaGVhdG1hcCANCmRyYXcod3lsZXJfaG1hcCwgDQogICAgICMgUGxvdCB0aGUgbGVnZW5kIG9uIHRoZSBib3R0b20NCiAgICAgaGVhdG1hcF9sZWdlbmRfc2lkZSA9ICJsZWZ0Ig0KICAgICkNCmBgYA0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMjIDIuMy4yIEdlbmVyYXRlIGEgay1tZWFucyBjbHVzdGVyIGFuYWx5aXMNCg0KTG9va3MgbGlrZSB0aGUgaGVhdG1hcCBzdGlsbCBkb2Vzbid0IHJldmVhbCBhIGxvdCBvZiBpbmZvcm1hdGlvbiB0byB1cyBsaWtlIGhvdyBzYW1wbGVzIG1pZ2h0IGJlIGdyb3VwZWQuIExldCdzIHByb2NlZWQgd2l0aCBrLW1lYW5zIGFuZCBzZWUgaWYgdGhhdCB3b3JrcyBiZXR0ZXIuIFdlIGp1c3QgbmVlZCB0byByZXBlYXQgb3VyIHN0ZXBzIG9uIGEgZGlmZmVyZW50IHNldCBvZiBkYXRhLiBTaW5jZSB3ZSBub3cgaGF2ZSBvdXIgbW9zdCBkaXZlcnNlIGdlbmVzIGFuZCB0aGVpciByZWFkY291bnRzLCB3ZSdsbCB0YWtlIGEgbG9vayBhdCBpZiB3ZSBjYW4gY2x1c3RlciB0aGlzIGluZm9ybWF0aW9uLg0KDQoxLiAgR2VuZXJhdGUgYW4gZXN0aW1hdGUgb24gdGhlIGFwcHJvcHJpYXRlIG51bWJlciBvZiBjbHVzdGVycw0KMi4gIENyZWF0ZSBvdXIgay1tZWFucyBjbHVzdGVyIG9iamVjdA0KMy4gIFZpc3VhbGl6ZSB0aGUgay1tZWFucyBvYmplY3QgYW5kIHNlZSB3aGljaCBleHBlcmltZW50cyB0ZW5kIHRvIGdyb3VwIHRvZ2V0aGVyLg0KDQpgYGB7cn0NCiMgSG93IG1hbnkgY2x1c3RlcnMgc2hvdWxkIHdlIGdlbmVyYXRlPw0KZnZpel9uYmNsdXN0KHQod3lsZXJfcmVhZGNvdW50cy5teCksIEZVTmNsdXN0ZXIgPSBrbWVhbnMsIG1ldGhvZD0id3NzIikNCmZ2aXpfbmJjbHVzdCh0KHd5bGVyX3JlYWRjb3VudHMubXgpLCBGVU5jbHVzdGVyID0ga21lYW5zLCBtZXRob2Q9InNpbGhvdWV0dGUiKQ0KZnZpel9uYmNsdXN0KHQod3lsZXJfcmVhZGNvdW50cy5teCksIEZVTmNsdXN0ZXIgPSBrbWVhbnMsIG1ldGhvZD0iZ2FwX3N0YXQiKQ0KYGBgDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQpGcm9tIHRoZSAzIG1ldGhvZHMgd2Ugc2VlIHRoYXQNCg0KMS4gIE91ciBXU1MgbWV0aG9kIHByb2R1Y2VkIGFuIGVsYm93IGFyb3VuZCBrID0gNA0KDQoyLiAgT3VyIHNpbGhvdWV0dGUgbWV0aG9kIHBlYWtzIGF0IGsgPSA0DQoNCjMuICBPdXIgZ2FwX3N0YXQgbWV0aG9kIHNlZXMgZGltaW5pc2hpbmcgcHJvZ3Jlc3MgYWxzbyBhdCBrID0gNA0KDQpMZXQncyBwcm9jZWVkIHdpdGggdGhhdCBpbiBtaW5kIQ0KDQpgYGB7cn0NCiMgQ29tcHV0ZSBrLW1lYW5zIHdpdGggayA9IDQNCg0KIyBTZXQgYSBzZWVkIGZvciByZXByb2R1Y2liaWxpdHkuDQpzZXQuc2VlZCgxMjMpDQoNCiMgR2VuZXJhdGUgb3VyIGstbWVhbnMgYW5hbHlzaXMNCnd5bGVyX3JlYWRjb3VudHMua20gPC0gDQprbWVhbnMoc2NhbGUoLi4uKSwgIyBXZSdsbCBzY2FsZSBvdXIgZGF0YSBmb3IgdGhpcyAobW9yZSBvbiB0aGF0IGxhdGVyIHRvbyEpDQogICAgICAgY2VudGVycyA9IDQsIA0KICAgICAgIG5zdGFydCA9IDI1LA0KICAgICAgIGl0ZXIubWF4ID0gNTAwKQ0KDQojIFRha2UgYSBsb29rIGF0IHRoZSByZXN1bHRpbmcgay1tZWFucyBvYmplY3QNCnN0cih3eWxlcl9yZWFkY291bnRzLmttKQ0KYGBgDQoNCmBgYHtyLCBmaWcud2lkdGg9MjAsIGZpZy5oZWlnaHQ9MTV9DQoNCiMgUGxvdCB0aGUgY2x1c3Rlcg0KIyBZb3UgY2FuIHRyZWF0IHRoaXMgb2JqZWN0IGxpa2UgYSBnZ3Bsb3Qgb2JqZWN0IGFmdGVyd2FyZHMNCg0Kd3lsZXJfa21lYW5zLnBsb3QgPC0NCg0KICBmdml6X2NsdXN0ZXIob2JqZWN0ID0gLi4uLCAjIG91ciBrLW1lYW5zIG9iamVjdA0KICAgICAgICAgICAgICAgZGF0YSA9IHQod3lsZXJfcmVhZGNvdW50cy5teCksICMgT3VyIG9yaWdpbmFsIGRhdGEgbmVlZGVkIGZvciBQQ0EgdG8gdmlzdWFsaXplDQogICAgICAgICAgICAgICBlbGxpcHNlLnR5cGUgPSAiY29udmV4IiwgDQogICAgICAgICAgICAgICBnZ3RoZW1lID0gdGhlbWVfYncoKSwgDQogICAgICAgICAgICAgICByZXBlbD1UUlVFLCAjIFRyeSB0byBhdm9pZCBvdmVybGFwcGluZyB0ZXh0DQogICAgICAgICAgICAgICBsYWJlbHNpemUgPSAyMCwNCiAgICAgICAgICAgICAgIHBvaW50c2l6ZSA9IDQsDQogICAgICAgICAgICAgICBtYWluID0gIkstbWVhbnMgY2x1c3RlcmluZyBvZiBmaWx0ZXJlZCBXeWxlciByZWFkY291bnQgZGF0YSINCiAgICAgICAgICAgICAgICkgKw0KDQogICMgU2V0IHNvbWUgZ2dwbG90IHRoZW1lIGluZm9ybWF0aW9uDQogIHRoZW1lKHRleHQgPSBlbGVtZW50X3RleHQoc2l6ZT0yMCkpICsNCg0KICAjIFNldCB0aGUgY29sb3VyIGFuZCBmaWxsIHNjaGVtZSB0byB2aXJpZGlzDQogIHNjYWxlX2ZpbGxfdmlyaWRpc19kKCkgKw0KICBzY2FsZV9jb2xvdXJfdmlyaWRpc19kKCkNCg0KIyBQcmludCB0aGUgcGxvdCENCnd5bGVyX2ttZWFucy5wbG90DQpgYGANCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjIyAyLjMuMyBJbnRlcnByZXRpbmcgb3VyIFJOQS1TZXEgY2x1c3RlcmluZw0KDQpMb29raW5nIGF0IHRoZSByZXN1bHQsIHdoYXQncyBuaWNlIGFib3V0IHRoaXMga2luZCBvZiBvdXRwdXQgaXMgdGhhdCB3ZSBjYW4gaW1tZWRpYXRlbHkgc2VlIHRoZSBzZXBhcmF0aW9uIG9yIGNsdXN0ZXJpbmcgb2Ygb3VyIGRhdGEuIEl0IGxvb2tzIGxpa2UgNCB3YXMgdGhlIHdheSB0byBnbyBhcyB0aGVyZSBhcmUgNCB0aWdodCBncm91cGluZ3MgZnJvbSBvdXIgZGF0YS4gSXQgbWlnaHQgYmUgcG9zc2libGUgdG8gZnVydGhlciBzdWJkaXZpZGUgdGhlIGdyb3VwIGluIHRoZSB0b3AgbGVmdCBvZiBvdXIgZmlndXJlIGJ1dCBpdCdzIHVubGlrZWx5Lg0KDQpCYXNlZCBvbiBvdXIgc2VsZWN0aW9uIG9mIGdlbmVzLCB3ZSBvYnNlcnZlOg0KDQoxLiAgMjQtLCA0OC0sIGFuZCA3Mi1ob3VyIHNhbXBsZXMgbW9jay10cmVhdGVkIChETVNPKSBhbmQgbW9jay1pbmZlY3RlZCwgY2x1c3RlciB0b2dldGhlciB3aXRoIDI0LWhvdXIgbW9jay10cmVhdGVkIHNhbXBsZXMgaW5mZWN0ZWQgd2l0aCBTQVJTLUNvVi0yLg0KDQoyLiAgNDgtIGFuZCA3Mi1ob3VyIHNhbXBsZXMgbW9jay10cmVhdGVkIChETVNPKSBhbmQgaW5mZWN0ZWQgYnkgU0FSUy1Db1YtMiBhcHBlYXIgdG8gc2hhcmUgYSBzaW1pbGFyIHByb2ZpbGUuDQoNCjMuICAyNC1ob3VyIHNhbXBsZXMgdHJlYXRlZCB3aXRoIDIwMCBuTSAxN0FBRyAoSFNQOTAgaW5oaWJpdG9yKSBjbHVzdGVyIHRvZ2V0aGVyIHdoZXRoZXIgb3Igbm90IHRoZXkgYXJlIGluZmVjdGVkIHdpdGggU0FSUy1Db1YtMi4NCg0KNC4gIDQ4LSBhbmQgNzItaG91ciBzYW1wbGVzIHRyZWF0ZWQgd2l0aCAyMDAgbk0gMTdBQUcgY2x1c3RlciB0b2dldGhlciB3aGV0aGVyIG9yIG5vdCB0aGV5IGFyZSBpbmZlY3RlZCB3aXRoIFNBUlMtQ29WLTIuDQoNClRoZXNlIGdyb3VwaW5ncyBzdWdnZXN0IHRoYXQgcGVyaGFwcyB0aGUgMjQtaG91ciBTQVJTLUNvVi1pbmZlY3RlZCB0aW1lcG9pbnQgaXMgc2ltaWxhciB0byB1bmluZmVjdGVkIGNvbnRyb2xzLiBNZWFud2hpbGUgdGhlIDQ4LSBhbmQgNzItaG91ciBTQVJTLUNvVi1pbmZlY3RlZCBzYW1wbGVzIGFyZSBzaW1pbGFyIGJ1dCBsaWtlbHkgc2hvd2luZyB2ZXJ5IGRpc3RpbmN0IHRyYW5zY3JpcHRpb25hbCBjaGFuZ2VzIGR1ZSB0byBpbmZlY3Rpb24uIFRyZWF0bWVudCB3aXRoIHRoZSBIU1A5MC1pbmhpYml0b3IsIGhvd2V2ZXIsIGFwcGVhcnMgdG8gc3VmZmljaWVudGx5IGFsdGVyIGV4cHJlc3Npb24gcHJvZmlsZXMgb2YgaW5mZWN0ZWQgYW5kIG1vY2staW5mZWN0ZWQgY2VsbHMgdG8gbWFrZXMgdGhlbSBjbHVzdGVyIHRvZ2V0aGVyLiBUaGlzIHdvdWxkIGJlIGEgZ29vZCBzdGFydGluZyBwb2ludCB0byBiZWdpbiBkaWdnaW5nIGZ1cnRoZXIgaW50byB0aGUgZGF0YS4NCg0KOjo6IHsuYWxlcnQgLmFsZXJ0LWJsb2NrIC5hbGVydC13YXJuaW5nfQ0KKipTa2VwdGljIG9yIGJlbGlldmVyPyoqIFdoaWxlIG91ciBhYm92ZSBhbmFseXNpcyBzZWVtcyB0byBtYWtlIHNlbnNlIHRvIHVzLCBpdCBsYWNrcyBhIGRlZXBlciB1bmRlcnN0YW5kaW5nIG9mIHdoYXQgbWlnaHQgYmUgaGFwcGVuaW5nLiBGb3Igb25lIHRoaW5nLCB3ZSBoYXZlbid0IGV2ZW4gbG9va2VkIGNsb3NlbHkgYXQgdGhlIGdlbmVzIHVzZWQgdG8gY3JlYXRlIG91ciBjbHVzdGVyaW5nLiBIb3cgbWFueSBvZiB0aGVtIGFyZSByZWxldmFudCB0byB0aGUgaW5mZWN0aW9uIHByb2Nlc3M/IElzIHRoZSBjbHVzdGVyaW5nIGR1ZSB0byBvZmYtdGFyZ2V0IGVmZmVjdHMgb2YgdGhlIEhTUDkwIGluaGliaXRvciAoMTdBQUcpPyBXaGlsZSBjbHVzdGVyaW5nIGlzIGFuIGVmZmVjdGl2ZSB3YXkgdG8gcXVpY2tseSBpZGVudGlmeSBpZiBzdWJncm91cHMgZXhpc3Qgd2l0aGluIHlvdXIgZGF0YSwgaXQncyBpbXBvcnRhbnQgdG8gZm9sbG93IHVwIHRoZXNlIGZpbmRpbmdzIHdpdGggaW4tZGVwdGggYW5hbHlzZXMgb2YgdGhlIGRhdGEuDQo6OjoNCg0KOjo6IHthbGlnbj0iY2VudGVyIn0NCjxpbWcgc3JjPSJodHRwczovL2dpdGh1Yi5jb20vY2Ftb2svQ1NCX0NvdXJzZV9NYXRlcmlhbHMvYmxvYi9tYWluL0FkdlZpei9zcXVpZF9QQ0EucG5nP3Jhdz10cnVlIiB3aWR0aD0iNzAwIi8+DQoNClllcyB0aGUgZGF0YSBjbHVzdGVycywgYnV0IHdoeSBkb2VzIGl0IGNsdXN0ZXI/DQo6OjoNCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMgMy4wLjAgRGltZW5zaW9uIHJlZHVjdGlvbiB0cmltcyB0aGUgKGRhdGEpIGZhdA0KDQpPbmUgcHJvYmxlbSB3ZSBlbmNvdW50ZXJlZCBpbiBvdXIgYWJvdmUgYW5hbHlzaXMgb2YgUk5BLVNlcSBkYXRhIGlzIHRoYXQgdGhlcmUgd2VyZSBzaW1wbHkgdG9vIG1hbnkgZGltZW5zaW9ucyEgV2l0aCBcfjI3ayBnZW5lIGVudHJpZXMgaW4gb3VyIGRhdGFmcmFtZSwgdXNpbmcgY2x1c3RlcmluZyB0byBhbmFseXNlIHRoZSBlbnRpcmUgZGF0YXNldCB3b3VsZCBiZSBtZW1vcnktaW50ZW5zaXZlIGlmIG5vdCBpbXBvc3NpYmxlIGFsdG9nZXRoZXIuIFRvIGNpcmN1bXZlbnQgdGhpcyBwcm9ibGVtIHdlIGF0dGVtcHRlZCB0byBzdWJzZXQgb3VyIGRhdGEgaW4gdHdvIHdheXMgLSBmaWx0ZXJpbmcgYnkgcmVhZCBjb3VudHMsIGFuZCBjb21wYXJpbmcgdmFyaWFuY2UgYWNyb3NzIGRhdGFzZXRzLiBUaGVzZSBhcHByb2FjaGVzIHdlcmUgYSBmb3JtIG9mICoqKmRpbWVuc2lvbmFsaXR5IHJlZHVjdGlvbioqKiAtIG5hbWVseSBmZWF0dXJlIGVsaW1pbmF0aW9uLiBPdXIgY2hvaWNlcywgaG93ZXZlciwgbWF5IGhhdmUgaW5hZHZlcnRlbnRseSBiZWVuIHRocm93aW5nIGF3YXkgZGF0YSB0aGF0IGNvdWxkIHByb3ZlIGluc2lnaHRmdWwhIFRoaXMgaXMgd2hlcmUgb3RoZXIgZGltZW5zaW9uYWxpdHkgcmVkdWN0aW9uIG1ldGhvZHMgY2FuIGhlbHAgZ3VpZGUgb3VyIGFuYWx5c2VzLg0KDQpZb3UgY2FuIGFjaGlldmUgZGltZW5zaW9uYWxpdHkgcmVkdWN0aW9uIGluIHRocmVlIGdlbmVyYWwgd2F5czoNCg0KMS4gICoqRmVhdHVyZSBlbGltaW5hdGlvbioqOiB5b3UgY2FuIHJlZHVjZSB0aGUgZmVhdHVyZSBzcGFjZSBieSByZW1vdmluZyB1bmhlbHBmdWwgZmVhdHVyZXMuIE5vIGluZm9ybWF0aW9uIGlzIGdhaW5lZCBidXQgeW91IHRyaW0gZG93biB5b3VyIGRhdGFzZXQgc2l6ZS4NCg0KMi4gICoqRmVhdHVyZSBzZWxlY3Rpb24qKjogb24gdGhlIHJldmVyc2Ugc2lkZSB5b3UgY2FuIHNpbXBseSBjaG9vc2UgdGhlIG1vc3QgaW1wb3J0YW50IGZlYXR1cmVzIHRvIHlvdS4gSG93IHdpbGwgeW91IGRlY2lkZT8gU29tZSBzY2hlbWVzIGluY2x1ZGUgcmFua2luZyB5b3VyIGZlYXR1cmVzIGJ5IGltcG9ydGFuY2UgYnV0IHRoaXMgbWF5IHN1ZmZlciBmcm9tIGluZm9ybWF0aW9uIGxvc3MgaWYgaW5jb3JyZWN0IGZlYXR1cmVzIGFyZSBjaG9zZW4uDQoNCjMuICAqKkZlYXR1cmUgZXh0cmFjdGlvbioqOiBjcmVhdGUgbmV3IGluZGVwZW5kZW50IGZlYXR1cmVzIHdoaWNoIGFyZSBhIGNvbWJpbmF0aW9uIG9mIHRoZSBvbGQgZmVhdHVyZXMhIEF0dGVtcHQgdG8gZXh0cmFjdCB0aGUgZXNzZW50aWFsIGZlYXR1cmVzIG9mIHlvdXIgZGF0YSBpbiBhIG1vcmUgaW5mb3JtYXRpb25hbHkgZGVuc2UgbWFubmVyLg0KDQp8IFRlY2huaXF1ZSAgICAgfCBEZXNjcmlwdGlvbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwgVHlwZSAgICAgICAgICAgICAgICAgICAgICAgICAgfCBVc2VmdWwgZm9yICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwNCnw6LS0tLS0tLS0tLS0tLS18Oi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tfDotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS18Oi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tfA0KfCBSYW5kb20gZm9yZXN0IHwgUG9wdWxhciBtYWNoaW5lIGxlYXJuaW5nIGFsZ29yaXRobSBmb3IgY2xhc3NpZmljYXRpb24gcmFuZG9tbHkgY2hvb3NlcyBmcm9tIHN1YnNldHMgb2YgZmVhdHVyZXMgdG8gY2xhc3NpZnkgZGF0YS4gVGhlIG1vc3QgZnJlcXVlbnRseSBhcHBlYXJpbmcgZmVhdHVyZXMgYW1vbmdzdCB0aGUgZm9yZXN0cyBhcmUgY2hvc2VuLiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8IEZlYXR1cmUgc2VsZWN0aW9uICAgICAgICAgICAgIHwgT25seSB0YWtlcyBudW1lcmljIGlucHV0cyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQp8IFBDQSAgICAgICAgICAgfCBQcmluY2lwYWwgY29tcG9uZW50IGFuYWx5c2lzIGF0dGVtcHRzIHRvIG1heGltaXplIHZhcmlhdGlvbiB3aGVuIHRyYW5zZm9ybWluZyB0byBhIGxvd2VyLWRpbWVuc2lvbmFsIHNwYWNlLiBBbGwgbmV3IGZlYXR1cmVzIGFyZSBpbmRlcGVuZGVudCBvZiBvbmUgYW5vdGhlci4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwgTGluZWFyIEZlYXR1cmUgRXh0cmFjdGlvbiAgICAgfCBBY3R1YWxseSByZWFsbHkgZ29vZCBhdCBmaW5kaW5nIG91dGxpZXJzIGluIFJOQXNlcSBkYXRhIHwNCnwgdC1TTkUgICAgICAgICB8IHQtRGlzdHJpYnV0ZWQgU3RvY2hhc3RpYyBOZWlnaGJvdXIgRW1iZWRkaW5nIGlzIGEgbm9uLWxpbmVhciB0ZWNobmlxdWUgc2ltaWxhciB0byBQQ0Egc3VpdGFibGUgZm9yIGhpZ2gtZGltZW5zaW9uIGRhdGFzZXRzLiBJdCBhdHRlbXB0cyB0byBtaW5pbWl6ZSBwcm9iYWJpbGl0aWVzIGluIG1pcnJvcmluZyBuZWFyZXN0IG5laWdoYm91ciByZWxhdGlvbnNoaXBzIGluIHRyYW5zZm9ybWluZyBmcm9tIGhpZ2ggdG8gbG93LWRpbWVuc2lvbiBzcGFjZXMgfCBOb24tbGluZWFyIEZlYXR1cmUgZXh0cmFjdGlvbiB8IERldGVybWluZSBpZiB5b3VyIGRhdGEgaGFzIHVuZGVybHlpbmcgc3RydWN0dXJlICAgICAgICAgfA0KfCBVTUFQICAgICAgICAgIHwgVW5pZm9ybSBtYW5pZm9sZCBhcHByb3hpbWF0aW9uIGFuZCBwcm9qZWN0aW9uIGlzIHByb2plY3Rpb24tYmFzZWQgbGlrZSB0LVNORSwgdGhpcyB0ZWNobmlxdWUgYXR0ZW1wdHMgdG8gcHJlc2VydmUgbG9jYWwgZGF0YSBzdHJ1Y3R1cmUgYnV0IGhhcyBpbXByb3ZlZCB0cmFuc2xhdGlvbiBvZiBnbG9iYWwgZGF0YSBzdHJ1Y3R1cmUuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8IE5vbi1saW5lYXIgZmVhdHVyZSBleHRyYWN0aW9uIHwgRmFzdGVyIHRoYW4gdC1TTkUgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQoNClNpbmNlIHdlJ3JlIG5vdCBleGFjdGx5IGJ1aWxkaW5nIGEgY2xhc3NpZmllciBidXQgcmF0aGVyIHRyeWluZyB0byBmaW5kIHRyZW5kcyBpbiBvdXIgZGF0YSwgd2Ugd29uJ3QgYmUgbG9va2luZyBhdCBSYW5kb20gRm9yZXN0cyBoZXJlLiBIZXJlIHdlIGFyZSBpbnRlcmVzdGVkIGluICpleHBsb3JhdG9yeSBkYXRhIGFuYWx5c2lzKi4gV2UgaGF2ZSBhIGRhdGEgc2V0IHdlIHdhbnQgdG8gdW5kZXJzdGFuZCwgc29tZXRpbWVzIGl0IGlzIHRvbyBjb21wbGV4IHRvIGp1c3QgcHJvamVjdCBvciBkaXZpbmUgMSkgdGhlIHVuZGVybHlpbmcgc3RydWN0dXJlIGFuZCAyKSB0aGUgZmVhdHVyZXMgdGhhdCBkcml2ZSB0aGF0IHN0cnVjdHVyZS4NCg0KOjo6IHsuYWxlcnQgLmFsZXJ0LWJsb2NrIC5hbGVydC1zdWNjZXNzfQ0KKipUaGVyZSdzIG1vcmUgdG8gZXhwbG9yZSB3aXRoIGRpbWVuc2lvbiByZWR1Y3Rpb246KiogVGhlIGFib3ZlIHRhYmxlIGlzIGp1c3QgYSBzbWFsbCBzdWJzZXQgb2YgcG90ZW50aWFsbHkgZGlmZmVyZW50IGtpbmRzIG9mIGRpbWVuc2lvbiByZWR1Y3Rpb24gbWV0aG9kcy4gUENBIGZvciBpbnN0YW5jZSBoYXMgMiAidmFyaWFudHMiOiBNdWx0aXBsZSBDb3JyZXNwb25kZW5jZSBBbmFseXNpcyAoTUNBKSB3aGljaCBkZWFscyB3aXRoIHJlbGF0aW9uc2hpcHMgYmV0d2VlbiBjYXRlZ29yaWNhbCB2YXJpYWJsZXMgcmF0aGVyIHRoYW4gY29udGludW91cyBvbmVzLCBhbmQgSW5kZXBlbmRlbnQgQ29tcG9uZW50IEFuYWx5c2lzIChJQ0EpIHdoaWNoIGFsc28gdXNlcyBsaW5lYXIgZGltZW5zaW9uIHJlZHVjdGlvbiB0byBpZGVudGlmeSBpbmRlcGVuZGVudCBjb21wb25lbnRzIGluIHlvdXIgZGF0YXNldC4gWW91IGNhbiBjaGVjayBvdXQgYSBsaXR0bGUgbW9yZSBbaGVyZV0oaHR0cHM6Ly93d3cuc3BpY2V3b3Jrcy5jb20vdGVjaC9hcnRpZmljaWFsLWludGVsbGlnZW5jZS9hcnRpY2xlcy93aGF0LWlzLWRpbWVuc2lvbmFsaXR5LXJlZHVjdGlvbi8pIGFuZCBbb3ZlciBoZXJlXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9EaW1lbnNpb25hbGl0eV9yZWR1Y3Rpb24pDQo6OjoNCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjIDMuMS4wIFJlZHVjZSB5b3VyIGRpbWVuc2lvbmFsaXR5IHdpdGggUENBDQoNCkdvaW5nIGJhY2sgdG8gb3VyIFBIVSBkYXRhLCB3ZSB1c2VkIDcgZGltZW5zaW9ucyB0byBjbGFzc2lmeSBvdXIgZGF0YSBidXQgd2hhdCBpZiB3ZSB3YW50ZWQgdG8gdHJhbnNmb3JtIHRoYXQgaW5mb3JtYXRpb24gaW4gc29tZSB3YXkgdG8gcmVkdWNlIHRoZSBudW1iZXIgb2YgZGltZW5zaW9ucyBuZWVkZWQgdG8gcmVwcmVzZW50IHRoZWlyIHJlbGF0aW9uc2hpcHM/IEZvciBvdXIgZGF0YSBzZXQsIDcgZGltZW5zaW9ucyBpc24ndCBhIGxvdCBvZiBmZWF0dXJlcyBidXQgaW4gb3RoZXIgY2FzZXMgKGxpa2UgUk5BLVNlcSkgeW91IG1pZ2h0IGVuY291bnRlciBmZWF0dXJlLWRlbnNlIGRhdGFzZXRzIGFuZCB3aXRob3V0IGtub3dpbmcgKmEgcHJpb3JpKiB3aGljaCBvbmVzIGFyZSBtZWFuaW5nZnVsLCBQQ0EgcHJvdmlkZXMgYSBwYXRoIGZvcndhcmQgaW4gY2xhc3NpZnlpbmcgb3VyIGRhdGEuDQoNCk5vdyB0aGVyZSBhcmUgY2F2ZWF0cyB0byBQQ0EuIEl0IHByb2R1Y2VzIGxpbmVhciBmZWF0dXJlIGV4dHJhY3Rpb24gYnkgYnVpbGRpbmcgbmV3IGZlYXR1cmVzIGluIHlvdXIgbi1kaW1lbnNpb25hbCBzcGFjZSBzdWNoIHRoYXQgZWFjaCBuZXcgZmVhdHVyZSAoa2luZCBvZiBsaWtlIGEgcHJvamVjdGVkIGxpbmUpIG1heGltaXplcyB2YXJpYW5jZSB0byB0aGUgb3JpZ2luYWwgZGF0YSBwb2ludHMuIEVhY2ggbmV3IGNvbXBvbmVudCBtdXN0IGJlIHVuY29ycmVsYXRlZCB3aXRoIChpZSBwZXJwZW5kaWN1bGFyIHRvKSB0aGUgcHJldmlvdXMgb25lcyB3aGlsZSBhY2NvdW50aW5nIGZvciB0aGUgbmV4dCBoaWdoZXN0IGFtb3VudCBvZiB2YXJpYW5jZS4gTW9yZSByZXNvdXJjZXMgb24gaG93IHRoaXMgd29ya3MgaW4gdGhlIHJlZmVyZW5jZXMuDQoNCjo6OiB7YWxpZ249ImNlbnRlciJ9DQo8aW1nIHNyYz0iaHR0cHM6Ly9naXRodWIuY29tL2NhbW9rL0NTQl9Db3Vyc2VfTWF0ZXJpYWxzL2Jsb2IvbWFpbi9BZHZWaXovUENBX3NlY29uZF9wcmluY2lwYWwuZ2lmP3Jhdz10cnVlIiB3aWR0aD0iOTAwIi8+DQoNCkhvdyBkbyB3ZSBnbyBhYm91dCBtYXhpbWl6aW5nIHRoZSB2YXJpYW5jZSAoc3ByZWFkIG9mIHJlZCBkb3RzKSB0byBvdXIgZGF0YSBmZWF0dXJlcz8gRmluZGluZyB0aGUgcG9pbnQgd2hlcmUgdmFyaWFuY2UgaXMgbWF4aW1pemVkLCBhbHNvIG1pbmltaXplcyBlcnJvciAocmVkIGxpbmUgbGVuZ3RocykuIEdlbmVyYXRlZCBieSB1c2VyIGBBbW9lYmFgIG9uIFtzdGFja2V4Y2hhbmdlLmNvbV0oaHR0cHM6Ly9zdGF0cy5zdGFja2V4Y2hhbmdlLmNvbS9xdWVzdGlvbnMvMjY5MS9tYWtpbmctc2Vuc2Utb2YtcHJpbmNpcGFsLWNvbXBvbmVudC1hbmFseXNpcy1laWdlbnZlY3RvcnMtZWlnZW52YWx1ZXMpDQo6OjoNCg0KQWxsIG1hdGggYXNpZGUsIG91ciBnb2FsIGlzIHRvIHJlZHVjZSBvdXIgZmVhdHVyZSBzZXQgdG8gc29tZXRoaW5nIHNtYWxsZXIgYnkgdHJ5aW5nIHRvIHJlcHJlc2VudCBvdXIgZGF0YSB3aXRoIHRoZXNlIG5ldyBmZWF0dXJlcy4gSnVzdCByZW1lbWJlciB0aGF0IGhpZ2hseSB2YXJpYWJsZSBkYXRhIGFuZCBvdXRsaWVycyBjYW4gZG9taW5hdGUgeW91ciBwcmluY2lwYWwgY29tcG9uZW50cy4NCg0KRm9yIHNpbXBsaWNpdHkgbGV0J3MgaGVhZCBiYWNrIGFuZCBsb29rIGF0IG91ciBQSFUgYWdlIGdyb3VwIGRhdGEgYWdhaW4uIFRvIGlsbHVzdHJhdGUgb3VyIGV4YW1wbGUgd2l0aCBQQ0EsIGxldCdzIHVzZSB0aGUgb3JpZ2luYWwgZGF0YSBub3JtYWxpemVkIGJ5IFBIVSBwb3B1bGF0aW9uLg0KDQpgYGB7cn0NCiMgVmlldyB0aGUgbm9ybWFsaXplZCBQSFUgYWdlIGdyb3VwIGRhdGENCmhlYWQoY292aWRfZGVtb2dyYXBoaWNzX25vcm0ubXgpDQpgYGANCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjIDMuMi4wIFVzZSBgUENBKClgIHRvIGdlbmVyYXRlIG91ciBhbmFseXNpcw0KDQpUbyBnZW5lcmF0ZSBvdXIgYW5hbHlzaXMgb2YgdGhlIFBIVSBkYXRhLCB3ZSdsbCB1c2UgdGhlIGBGYWN0b01pbmVSYCBmdW5jdGlvbiBgUENBKClgIGZvciB3aGljaCB0aGVyZSBhcmUgc29tZSBwYXJhbWV0ZXJzIHdlJ2xsIGJlIHVzaW5nIHRoYXQgd2Ugc2hvdWxkIGRpc2N1c3M6IC0gYFhgIGEgZGF0YSBmcmFtZSBvZiAqbiogcm93cyAob2JzZXJ2YXRpb25zKSBhbmQgKnAqIGNvbHVtbnMgKG51bWVyaWMgdmFyaWFibGVzKSAtIGBuY3BgIHRoZSBudW1iZXIgb2YgZGltZW5zaW9ucyBrZXB0IGluIHRoZSByZXN1bHRzICg1IGlzIHRoZSBkZWZhdWx0KSAtIGBzY2FsZS51bml0YCBhIGJvb2xlYW4gdG8gc2NhbGUgeW91ciBkYXRhIChUUlVFIGJ5IGRlZmF1bHQpDQoNCiMjIyAzLjIuMSBXaGF0IGRvZXMgaXQgbWVhbiB0byBzY2FsZSBvdXIgZGF0YT8NCg0KUmVtZW1iZXIgdGhhdCBQQ0EgaXMgdHJ5aW5nIHRvIG1heGltaXplIGRpc3RhbmNlIGJldHdlZW4gYSBwcmluY2lwbGUgY29tcG9uZW50IGFuZCBhbGwgb2YgdGhlIG9ic2VydmF0aW9ucyBzdXBwbGllZC4gRGVwZW5kaW5nIG9uIHRoZSBuYXR1cmUgb2YgeW91ciB2YXJpYWJsZXMgeW91IG1heSBoYXZlLCBmb3IgaW5zdGFuY2UsIHR3byBkaWZmZXJlbnQgdW5pdCB0eXBlcyBsaWtlIGhlaWdodCBhbmQgbWFzcy4gU21hbGxlciBjaGFuZ2VzIGluIGhlaWdodCBtYXkgYmUgbWF0Y2hlZCB3aXRoIG11Y2ggbGFyZ2VyIGNoYW5nZXMgaW4gbWFzcyBvciBqdXN0IHdpZGVyIG92ZXJhbGwgdmFyaWFuY2UuIFRoaXMgbWF5IGxlYWQgdGhlIFBDQSBhbGdvcml0aG0gdG8gcHJpb3JpdGl6ZSBtYXNzIG92ZXIgaGVpZ2h0IHdoZW4geW91J2QgcHJlZmVyIHRoZXkgaGF2ZSBhbiBlcXVhbCBpbXBvcnRhbmNlLiBCeSBjZW50ZXJpbmcgeW91ciBtZWFuIGFuZCBzY2FsaW5nIGRhdGEgdG8gdW5pdCB2YXJpYW5jZSwgZXZlcnl0aGluZyBpcyBjb21wYXJlZCBhcyBhICoqei1zY29yZSoqLCBicmluZ2luZyB0aGUgb3ZlcmFsbCB2YXJpYW5jZSBhY3Jvc3MgYSB2YXJpYWJsZSB0byB3aXRoaW4gXH4zIHN0YW5kYXJkIGRldmlhdGlvbnMuDQoNCkxldCdzIGNvbXBhcmUgUENBIHdpdGggYW5kIHdpdGhvdXQgc2NhbGluZyBzaGFsbCB3ZT8NCg0KYGBge3J9DQojIEJ1aWxkIGEgUENBIG9mIG91ciBQSFUgZGF0YSB3aXRoIHNjYWxpbmcgYXBwbGllZA0KcGh1X3NjYWxlZC5wY2EgPC0gUENBKC4uLiwgDQogICAgICAgICAgICAgICAgICAgICAgc2NhbGUudW5pdCA9IC4uLiwgIyBXaGF0IGhhcHBlbnMgd2hlbiB3ZSBkb24ndCBzY2FsZSB0aGUgZGF0YT8NCiAgICAgICAgICAgICAgICAgICAgICBuY3AgPSAuLi4sDQogICAgICAgICAgICAgICAgICAgICAgZ3JhcGggPSBUUlVFKQ0KDQojIEJ1aWxkIGEgUENBIG9mIG91ciBQSFUgZGF0YSBXSVRIT1VUIHNjYWxpbmcgYXBwbGllZA0KcGh1X3Vuc2NhbGVkLnBjYSA8LSBQQ0EoY292aWRfZGVtb2dyYXBoaWNzX25vcm0ubXhbLC0xXSwgDQogICAgICAgICAgICAgICAgICAgICAgICBzY2FsZS51bml0ID0gLi4uLA0KICAgICAgICAgICAgICAgICAgICAgICAgbmNwID0gNywNCiAgICAgICAgICAgICAgICAgICAgICAgIGdyYXBoID0gVFJVRSkNCmBgYA0KDQpgYGB7cn0NCiMgVGFrZSBhIGxvb2sgYXQgdGhlIGluZm9ybWF0aW9uIGluc2lkZSBvdXIgUENBIG9iamVjdA0KcHJpbnQoLi4uKQ0KYGBgDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyMgMy4yLjIgT3VyIFBDQSBvYmplY3QgaGFzIGEgY29tcGxleCBudW1iZXIgb2YgZGF0YSBwaWVjZXMNCg0KUmVnYXJkbGVzcyBvZiBzY2FsaW5nLCB0aGUgcmVzdWx0IG9mIG91ciBgUENBKClgIGNhbGwgcHJvZHVjZXMgYW4gb2JqZWN0IHdpdGggbWFueSB2YXJpYWJsZXMgd2UgY2FuIGFjY2Vzcy4gQWJvdmUgeW91IGNhbiBzZWUgYSBicmllZiBkZXNjcmlwdGlvbiBmb3IgZWFjaCB2YXJpYWJsZSBidXQgd2UgYXJlIG1vc3QgaW50ZXJlc3RlZCBpbiBhIGZldyBwYXJ0aWN1bGFyIG9uZXM6DQoNCi0gICBgZWlnYCBob2xkcyBvdXIgZGltZW5zaW9uYWwgZGF0YSBidXQgYWxzbyBkZXNjcmliZXMganVzdCBob3cgbXVjaCBlYWNoIG5ldyBwcmluY2lwbGUgY29tcG9uZW50IGRlc2NyaWJlcyB0aGUgb3ZlcmFsbCB2YXJpYXRpb24gb2Ygb3VyIGRhdGEuDQoNCi0gICBgdmFyYCBob2xkcyB0aGUgcmVzdWx0cyBvZiBhbGwgdGhlIHZhcmlhYmxlcy4gV2UgY2FuIHVzZSB0aGVzZSB0byBncmFwaCBhbmQgdmlzdWFsaXplIG91ciBkYXRhLg0KDQotICAgYGluZCRjb29yZGAgd2lsbCBhbGxvdyB1cyB0byBwbG90IHRoZSBjb29yZGluYXRlcyBvZiBvdXIgb2JzZXJ2YXRpb25zIGFsb25nIHRoZSBwcmluY2lwYWwgY29tcG9uZW50cy4NCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjIDMuMy4wIFVzZSB0aGUgZWlnZW52YWx1ZXMgdG8gZGV0ZXJtaW5lIHRoZSBwZXJjZW50IHZhcmlhbmNlIG9mIGVhY2ggY29tcG9uZW50DQoNClRoZSBlaWdlbnZhbHVlcyBmcm9tIG91ciBhbmFseXNpcyBwYWlyIHdpdGggdGhlIGVpZ2VudmVjdG9ycyAocHJpbmNpcGxlIGNvbXBvbmVudHMpIHRvIGhlbHAgdHJhbnNmb3JtIG91ciBkYXRhIGZyb20gdGhlIG9yaWdpbmFsIGZlYXR1cmUgc2V0IHRvIHRoZSBuZXcgc2V0IG9mIGZlYXR1cmVzLiBXaGlsZSB0aGUgZWlnZW52ZWN0b3JzIG1heSBkZXRlcm1pbmUgdGhlIGRpcmVjdGlvbnMgb2YgdGhlIG5ldyBmZWF0dXJlIHNwYWNlLCB0aGUgKmVpZ2VudmFsdWUqIHJlcHJlc2VudHMgdGhlICoqbWFnbml0dWRlKiogb2YgdGhlIHZlY3RvciBhbmQgaW4gdGhpcyBjYXNlIGNhbiBiZSB1c2VkIHRvIGNhbGN1bGF0ZSB0aGUgcGVyY2VudCBvZiBvdmVyYWxsIHZhcmlhbmNlIGV4cGxhaW5lZCBieSBvdXIgZWlnZW52ZWN0b3IuDQoNClRoZSBpbXBvcnRhbnQgdGFrZS1hd2F5IGlzIHRoYXQgd2UgY2FuIG5vdyBzZWUganVzdCBob3cgbXVjaCBvZiBvdXIgdmFyaWFuY2UgaXMgZXhwbGFpbmVkIGluIGVhY2ggbmV3IHByaW5jaXBsZSBjb21wb25lbnQuIFdlIGNhbiBhY2Nlc3MgdGhpcyBpbmZvcm1hdGlvbiBkaXJlY3RseSBmcm9tIGBwaHVfc2NhbGVkLnBjYWAgb3IgYnkgdXNpbmcgdGhlIGZ1bmN0aW9uIGBnZXRfZWlnZW52YWx1ZSgpYC4gV2UgY2FuIGFsc28gcGxvdCB0aGlzIGFzIGEgYmFyY2hhcnQgaW5zdGVhZCB1c2luZyBgZnZpel9laWcoKWAuDQoNCmBgYHtyfQ0KIyBMb29rIGF0IG91ciBlaWdlbnZhbHVlcyBkaXJlY3RseQ0KcGh1X3NjYWxlZC5wY2EkLi4uDQoNCiMgVXNlIGdldF9laWdlbnZhbHVlKCkgdG8gbG9vayBhdCBvdXIgZWlnZW52YWx1ZXMNCmdldF9laWdlbnZhbHVlKHBodV9zY2FsZWQucGNhKQ0KYGBgDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyMgMy4zLjEgQnVpbGQgYSBzY3JlZSBwbG90IHRvIGxvb2sgYXQgdGhlIGVmZmVjdCBvZiB5b3VyIGRpbWVuc2lvbnMNCg0KU2VlIGhvdyBuZWFybHkgODAlIG9mIG91ciB2YXJpYXRpb24gaXMgZXhwbGFpbmVkIGluIGp1c3Qgb3VyIGZpcnN0IGRpbWVuc2lvbj8gTGV0J3MgdXNlIGBmdml6X2VpZygpYCB0byBkaXNwbGF5IHRoaXMgaW5mb3JtYXRpb24gdmlzdWFsbHkgaW4gd2hhdCBpcyBrbm93biBhcyBhIHNjcmVlIHBsb3QuIEl0J3MgZXNzZW50aWFsbHkgYSBiYXJwbG90L2xpbmVwbG90IGNvbWJvIGJ1dCB3aGF0IHdlJ3JlIGludGVyZXN0ZWQgaW4gaXMgZm9sbG93aW5nIHRoZSBsaW5lcyBtdWNoIGxpa2Ugb3VyIGNsdXN0ZXItZXN0aW1hdGluZyBXU1MgbWV0aG9kLiBXZSB1c2UgYSAic2hhcnAgZWxib3ciIHRvIGRldGVybWluZSBob3cgbWFueSBwcmluY2lwYWwgY29tcG9uZW50cyB3ZSBuZWVkLg0KDQpgYGB7cn0NCg0KIyBWaXN1YWxpemUgdGhlIGltcGFjdCBvZiBvdXIgZWlnZW52YWx1ZXMNCmZ2aXpfZWlnKC4uLiwgYWRkbGFiZWxzID0gVFJVRSkgKyB0aGVtZSh0ZXh0ID0gZWxlbWVudF90ZXh0KHNpemU9MTApKQ0KYGBgDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyMgMy4zLjIgTW9zdCBvZiBvdXIgdmFyaWFuY2UgaXMgYWNjb3VudGVkIGZvciBpbiB0aGUgZmlyc3QgdHdvIHByaW5jaXBsZSBjb21wb25lbnRzIQ0KDQpMb29raW5nIGF0IG91ciBlaWdlbnZhbHVlcyB3ZSBjYW4gc2VlIHRoYXQgZXZlbiB0aG91Z2ggNyBuZXcgUENzIHdlcmUgZ2VuZXJhdGVkLCB0aGUgZmlyc3QgdHdvIGV4cGxhaW4gYWxtb3N0IDkyJSBvZiBvdXIgdmFyaWFuY2UuIFRoYXQgc3VnZ2VzdHMgdGhhdCB3aGF0ZXZlciAqbGluZWFyKiBzZXBhcmF0aW9uIGluIG91ciBkYXRhIGV4aXN0cywgd2UgY2FuIHJlY3JlYXRlIGluIGEgdHdvLWRpbWVuc2lvbmFsIHByb2plY3Rpb24gb2YgY29vcmRpbmF0ZXMgZnJvbSBQQzEgYW5kIFBDMi4gVGhpcyBzdWdnZXN0cyB0aGF0IHdlIGNhbiByZWNyZWF0ZSB0aGUgdW5kZXJseWluZyBzdHJ1Y3R1cmUgb2Ygb3VyIGRhdGEgd2l0aCB0aGVzZSB0d28gbmV3IGZlYXR1cmVzIGluc3RlYWQgb2YgdXNpbmcgYSA3LWRpbWVuc2lvbmFsIHNwYWNlIQ0KDQpUaGlzIG1ha2VzIHNvbWUgc2Vuc2Ugd2hlbiB3ZSB0YWtlIGEgY2xvc2VyIGxvb2sgYXQgdGhlIGRhdGEuIEZvciBpbnN0YW5jZSwgdGhlcmUgaXNuJ3QgbXVjaCB2aXN1YWwgaW5mb3JtYXRpb24gZm91bmQgaW4gb3VyIGAwIHRvIDRgIGFnZSByYW5nZS4gVGhlIHNtYWxsIGRpc3RyaWJ1dGlvbiBvZiBpbmZvcm1hdGlvbiBpbiB0aGVzZSBmZWF0dXJlcyBtYXkgbm90IGRvIG11Y2gsIGluIHRoZSBncmFuZCBzY2hlbWUsIHRvIGNoYW5nZSBob3cgdGhlIFBIVXMgYXJlIHNlcGFyYXRlZCBmcm9tIGVhY2ggb3RoZXIuIFRoZW4gYWdhaW4sIHBlcmhhcHMgdGhleSAqZG8qIGNvbnRhaW4gaW5mb3JtYXRpb24gdGhhdCB3ZSBzaW1wbHkgY2Fubm90IHNlZSBlYXNpbHkhIEhvdyB3aWxsIHdlIGtub3c/DQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyAzLjQuMCBJbnZlc3RpZ2F0ZSB5b3VyIHZhcmlhYmxlcyB3aXRoIGBnZXRfcGNhX3ZhcigpYA0KDQpXaXRoaW4gb3VyIFBDQSwgd2UgY2FuIGFjY2VzcyBpbmZvcm1hdGlvbiByZWdhcmRpbmcgaG93IG91ciBvcmlnaW5hbCB2YXJpYWJsZXMgYXJlIHRyYW5zZm9ybWVkIGludG8gdGhlIG5ldyBzcGFjZS4gVGhpcyBpcyBhbGwgc3RvcmVkIGluIHRoZSBgdmFyYCBlbGVtZW50IG9mIG91ciBQQ0Egb2JqZWN0LiBXZSBjYW4gZXh0cmFjdCB0aGUgYXNwZWN0cyBvZiB0aGlzIHVzaW5nIHRoZSBgZ2V0X3BjYV92YXIoKWAgYW5kIHZpc3VhbGl6ZSB0aGVzZSB0byBkZXRlcm1pbmUgdGhlIHF1YWxpdHkgb2Ygb3VyIHZhcmlhYmxlcyBhbmQgdGhlaXIgcmVwcmVzZW50YXRpb24gYnkgdGhlIG5ldyBwcmluY2lwYWwgY29tcG9uZW50cy4NCg0KYGBge3J9DQojIFdoYXQgaXMgdGhlIGluZm9ybWF0aW9uIGFzc29jaWF0ZWQgd2l0aCBvdXIgb3JpZ2luYWwgdmFyaWFibGVzDQpwaHUudmFyIDwtIGdldF9wY2FfdmFyKC4uLikNCg0KcGh1LnZhcg0KYGBgDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyMgMy40LjEgV2hhdCBpcyB0aGUgY29udHJpYnV0aW9uIG9mIGVhY2ggdmFyaWFibGUgdG8gdGhlIG5ldyBjb21wb25lbnRzPw0KDQpFYWNoIG9yaWdpbmFsIHZhcmlhYmxlIG1heSwgdG8gc29tZSBkZWdyZWUsIGluZmx1ZW5jZSB0aGUgYW1vdW50IG9mIGluZm9ybWF0aW9uIGNhcHR1cmVkIGludG8gZWFjaCBuZXcgcHJpbmNpcGFsIGNvbXBvbmVudC4gV2hlbiB3ZSBsb29rIGF0IGVhY2ggd2UgY2FuIHNlZSB0aGUgYnJlYWtkb3duIG9mIHZhcmlhYmxlcywgd2hpY2ggaW4gc29tZSBjYXNlcywgY2FuIHJldmVhbCB0byB1cyB3aGVyZSBtb3JlIG9yIGxlc3MgdmFyaWF0aW9uIGlzIGZvdW5kIGFzIHdlbGwuDQoNCmBgYHtyfQ0KIyBXaGF0IGlzIHRoZSBjb250cmlidXRpb24gb2YgZWFjaCB2YXJpYWJsZT8NCnBodS52YXIkLi4uDQpgYGANCg0KRnJvbSBhYm92ZSB3ZSBjYW4gc2VlIHRoYXQgb3VyIG9yaWdpbmFsIHZhcmlhYmxlcyBlcXVhbGx5IGNvbnRyaWJ1dGUgdG8gb3VyIGZpcnN0IHByaW5jaXBhbCBjb21wb25lbnQgYnV0IHRoZXJlIGlzIG11Y2ggbW9yZSBjb250cmlidXRpb24gaW4gUEMyIGZyb20gdGhyZWUgdmFyaWFibGVzOiAwIHRvIDQsIDUgdG8gMTEgYW5kIDgwKy4gRnJvbSBvdXIgcHJldmlvdXMgc2NyZWUgcGxvdCB0aGF0IG1lYW5zIHRoYXQgMTIuOCUgb2Ygb3ZlcmFsbCB2YXJpYXRpb24sIHdoaWNoIGlzIGRlc2NyaWJlZCBpbiBQQzIsIGlzIG1haW5seSBmcm9tIHRoZXNlIHRocmVlIGdyb3Vwcy4NCg0KOjo6IHsuYWxlcnQgLmFsZXJ0LWJsb2NrIC5hbGVydC1zdWNjZXNzfQ0KKipIb3cgbWFueSBkaW1lbnNpb25zIHNob3VsZCBJIGdldCBhbmQgaG93IG1hbnkgZG8gSSBrZWVwPyoqIEl0IGlzIGF0IHRoaXMgcG9pbnQgdGhhdCB3ZSBzaG91bGQgZGlzY3VzcyB0aGUgKm1heGltdW0qIG51bWJlciBvZiBwb3NzaWJsZSBkaW1lbnNpb25zLiBHaXZlbiAqKipuKioqIG9ic2VydmF0aW9ucywgYW5kICoqKnAqKiogZmVhdHVyZXMsIHRoZSBtYXhpbXVtIG51bWJlciBvZiBkaW1lbnNpb25zIGFmdGVyIHJlZHVjdGlvbiBpcyAqKiptaW4obi0xLCBwKSoqKi4gSW4gdGVybXMgb2YgaG93IG1hbnkgZGltZW5zaW9ucyB5b3Uga2VlcCwgeW91IHNob3VsZCBjb25zaWRlciB0aGUgZ2VuZXJhbCBydWxlIG9mIHRodW1iIHRoYXQgKioqYXQgbGVhc3QgODAlKioqIG9mIHlvdXIgdmFyaWF0aW9uIHNob3VsZCBiZSBhY2NvdW50ZWQgZm9yIGJ5IHRoZSBwcmluY2lwYWwgY29tcG9uZW50cyB0aGF0IHlvdSBjaG9vc2UuIEtlZXAgdGhhdCBpbiBtaW5kIQ0KOjo6DQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyMgMy40LjIgQ2hlY2tpbmcgdGhlIHF1YWxpdHkgYW5kIHJlcHJlc2VudGF0aW9uIG9mIHlvdXIgdmFyaWFibGVzIGluIHRoZSBQQ0Egd2l0aCBgY29vcmRgIGFuZCBgY29yYA0KDQpGcm9tIHRoZSByZW1haW5pbmcgdmFyaWFibGUgaW5mb3JtYXRpb24sIGBjb29yZGAgYW5kIGBjb3JgIGNhbiBib3RoIGJlIHVzZWQgdG8gcGxvdCBvdXIgb3JpZ2luYWwgdmFyaWFibGVzIGFsb25nIGRpZmZlcmVudCBwYWlycyBvZiBwcmluY2lwYWwgY29tcG9uZW50cy4gVGhlc2UgdmFsdWVzIGFyZSB0aGUgc2FtZSBiZWNhdXNlIHRoaXMgaXMgbm90IGEgKnByb2plY3Rpb24qIG9mIG91ciB2YXJpYWJsZXMgb250byB0aGUgUENzIGJ1dCBhIGNvcnJlbGF0aW9uIG9mIHRoZW0hIFRoZSBkaXN0YW5jZSBiZXR3ZWVuIHRoZSBvcmlnaW4gYW5kIHRoZSB2YXJpYWJsZSBjb29yZGluYXRlcyBnYXVnZXMgdGhlIHF1YWxpdHkgb2YgdGhlIHZhcmlhYmxlcyBvbiB0aGUgbWFwLiBUaGlzIG51bWJlciBpcyBzdW1tYXJpemVkIGluIHRoZSBgY29zMmAgKHNxdWFyZWQgY29zaW5lKSBlbGVtZW50Lg0KDQpgYGB7cn0NCiMgTG9vayBhdCB0aGUgY29vcmRpbmF0ZXMgb2Ygb3VyIHZhcmlhYmxlcyBhY3Jvc3MgdGhlIFBDcy4NCnBodS52YXIkLi4uDQpwaHUudmFyJC4uLg0KYGBgDQoNCmBgYHtyfQ0KIyBXaGF0IGlzIHRoZSBxdWFsaXR5IG9mIG91ciB2YXJpYWJsZXM/DQojIFRoZSBoaWdoZXIgdGhlIHZhbHVlIHRoZSBiZXR0ZXIhDQpwaHUudmFyJC4uLg0KYGBgDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyMgMy40LjMgUGxvdCB5b3VyIHZhcmlhYmxlIGluZm9ybWF0aW9uIG9uIGEgdW5pdCBjaXJjbGUgd2l0aCBgZnZpel9wY2FfdmFyKClgDQoNClRvIGNhcHR1cmUgYWxsIG9mIG91ciBpbmZvcm1hdGlvbiBpbiBhIHNpbmdsZSB2aXN1YWxpemF0aW9uIHdlIGNhbiB1c2UgYGZ2aXpfcGNhX3ZhcigpYCB0byBhc3Nlc3Mgb3VyIHZhcmlhYmxlcy4gVGhlIGBmdml6X3BjYV92YXIoKWAgZnVuY3Rpb24gd2lsbCBwbG90IGNvczIgdmFsdWVzIG9uIGEgMi1kaW1lbnNpb24gYXhpcyBvZiB5b3VyIGNob29zaW5nLiBIaWdoIHF1YWxpdHkgdmFyaWFibGVzIHJlcHJlc2VudGVkIGJ5IHRoZSB0d28gZGltZW5zaW9ucyBvZiB5b3VyIGNpcmNsZSAoaWUgUEMxIHZzIFBDMikgd2lsbCBiZSBjbG9zZXIgdG8gdGhlIGNpcmN1bWZlcmVuY2Ugb2YgdGhlIGNpcmNsZS4NCg0KU29tZSB2YXJpYWJsZXMgbWF5IHJlcXVpcmUgbW9yZSB0aGFuIDIgY29tcG9uZW50cyB0byByZXByZXNlbnQgdGhlIGRhdGEgYW5kIHRoZXkgd2lsbCB0aGVyZWZvcmUgZmFsbCBpbnNpZGUgdGhlIGNpcmNsZS4gTG93IHF1YWxpdHkgY29ycmVsYXRpb25zIHdpbGwgYmUgY2xvc2VyIHRvIHRoZSBvcmlnaW4gc2luY2UgdGhleSBjb3JyZWxhdGUgbGVzcyB3aXRoIHRoZSB0d28gZGltZW5zaW9ucyByZXByZXNlbnRlZCBvbiB0aGUgZ3JhcGguDQoNCkFzIHdlIHdpbGwgdmVyaWZ5IGZyb20gYWJvdmUsIG1vc3Qgb2Ygb3VyIHZhcmlhYmxlcyBhcmUgcG9zaXRpdmVseSBjb3JyZWxhdGVkIHdpdGggUEMxLiBBY3Jvc3MgYm90aCBQQzEvUEMyLCBvdXIgdmFyaWFibGVzIGNvcnJlbGF0ZSBoaWdobHkuDQoNClR3byBpbXBvcnRhbnQgcGFyYW1ldGVycyB0byBrZWVwIGluIG1pbmQ6DQoNCi0gICBgWGA6IHRoZSBQQ0Egb2JqZWN0IHRoYXQgeW91IHdhbnQgdG8gY3JlYXRlLg0KDQotICAgYGF4ZXNgOiBhIG51bWVyaWMgdmVjdG9yIHdpdGggdGhlIHR3byBkaW1lbnNpb25zIHlvdSB3YW50IHRvIGV4YW1pbmUuDQoNCmBgYHtyfQ0KIyBDb21wYXJlIGhvdyBvdXIgdmFyaWFibGVzIGNvbnRyaWJ1dGUgYW5kIGNvcnJlbGF0ZSB3aXRoIFBDMS9QQzINCmZ2aXpfcGNhX3ZhcihYID0gcGh1X3NjYWxlZC5wY2EsIA0KICAgICAgICAgICAgIGNvbC52YXIgPSAuLi4sICMgSG93IHdpbGwgd2UgY29sb3VyIG91ciBkYXRhL2xpbmVzDQogICAgICAgICAgICAgZ3JhZGllbnQuY29scyA9IGMoImdyZWVuIiwgInllbGxvdyIsICJyZWQiKSwgDQogICAgICAgICAgICAgbGFiZWxzaXplID0gNiwNCiAgICAgICAgICAgICByZXBlbCA9IFRSVUUsICMgbWFrZSBzdXJlIHRleHQgZG9lc24ndCBvdmVybGFwDQogICAgICAgICAgICAgYXhlcyA9IGMoMSwyKSAjIERldGVybWluZSB3aGljaCBQQ3MgeW91IHdhbnQgdG8gZ3JhcGgNCiAgICAgICAgICAgICkgKyANCiAgICB0aGVtZSh0ZXh0ID0gZWxlbWVudF90ZXh0KHNpemU9MTApKQ0KYGBgDQoNCmBgYHtyfQ0KIyBDb21wYXJlIGhvdyBvdXIgdmFyaWFibGVzIGNvbnRyaWJ1dGUgYW5kIGNvcnJlbGF0ZSB3aXRoIFBDMi9QQzMNCmZ2aXpfcGNhX3ZhcihwaHVfc2NhbGVkLnBjYSwgDQogICAgICAgICAgICAgY29sLnZhciA9ICJjb250cmliIiwgIyBIb3cgd2lsbCB3ZSBjb2xvdXIgb3VyIGRhdGEvbGluZXMNCiAgICAgICAgICAgICBncmFkaWVudC5jb2xzID0gYygiZ3JlZW4iLCAieWVsbG93IiwgInJlZCIpLCANCiAgICAgICAgICAgICBsYWJlbHNpemUgPSA2LA0KICAgICAgICAgICAgIHJlcGVsID0gVFJVRSwgIyBtYWtlIHN1cmUgdGV4dCBkb2Vzbid0IG92ZXJsYXANCiAgICAgICAgICAgICBheGVzID0gYyguLi4pICMgRGV0ZXJtaW5lIHdoaWNoIFBDcyB5b3Ugd2FudCB0byBncmFwaA0KICAgICAgICAgICAgKSArIA0KICAgIHRoZW1lKHRleHQgPSBlbGVtZW50X3RleHQoc2l6ZT0xMCkpDQpgYGANCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjIDMuNS4wIENoZWNrIGlmIFBIVXMgY2x1c3RlciBvciBzZXBhcmF0ZSB3aXRoIGBmdml6X3BjYV9pbmQoKWANCg0KTm93IHRoYXQgd2UndmUgZ2VuZXJhdGVkIG91ciBQQ0EsIGxldCdzIHNlZSBpZiB0aGVyZSBpcyBhbnkgY2xlYXIgc2VwYXJhdGlvbiBiZXR3ZWVuIG91ciBQSFVzIGFjcm9zcyB0aGUgZmlyc3QgdHdvIGRpbWVuc2lvbnMgb2YgbmV3IGZlYXR1cmVzLiBNdWNoIGxpa2Ugb3VyIHZhcmlhYmxlcywgYWxsIG9mIHRoZSBpbmRpdmlkdWFsIGNvb3JkaW5hdGUgZGF0YSBjYW4gYmUgZm91bmQgaW4gb3VyIGBpbmRgIGVsZW1lbnQgb2Ygb3VyIFBDQSBvYmplY3QuIFdpdGhpbiB0aGVyZSwgd2UnbGwgZmluZCB0aGUgYGNvb3JkYCBpbmZvcm1hdGlvbiBzbyB3ZSBjb3VsZCBkaXJlY3RseSBidWlsZCBhIGdncGxvdCB3aXRoIGBwaHVfc2NhbGVkLnBjYSRpbmQkY29vcmRgIEJVVCB0aGVyZSdzIGEgc2ltcGxlciB3YXkuDQoNCldlJ2xsIHBhcnNlIHRocm91Z2ggdGhlIFBDQSBvYmplY3Qgd2l0aCBgZnZpel9wY2FfaW5kKClgIHRvIHBsb3Qgb3VyIGRhdGEgYWxvbmcgdGhlIGZpcnN0IHR3byBwcmluY2lwbGUgY29vcmRpbmF0ZXMuIFdlIGNhbiBkZWZpbmUgYHBvaW50c2l6ZWAgYW5kIGBjb2wuaW5kYCB0byBoZWxwIGFkZCBzb21lIHBvcHVsYXRpb24gc2l6ZSBpbmZvcm1hdGlvbiB0byBvdXIgUEhVcy4NCg0KYGBge3IsIGZpZy53aWR0aD0xNiwgZmlnLmhlaWdodD0xMn0NCg0KIyBHcmFwaCBvdXIgc2NhbGVkIFBDQSBkYXRhLg0KdGVzdCA8LQ0KZnZpel9wY2FfaW5kKC4uLiwgDQogICAgICAgICAgICAgcG9pbnRzaXplID0gY292aWRfZGVtb2dyYXBoaWNzX25vcm0ubXhbLDFdLCAjIFNldCBwb2ludCBzaXplIGFuZCBjb2xvdXIgYnkgUEhVIHBvcHVsYXRpb24NCiAgICAgICAgICAgICBjb2wuaW5kID0gbG9nMTAoY292aWRfZGVtb2dyYXBoaWNzX25vcm0ubXhbLDFdKSwNCiAgICAgICAgICAgICByZXBlbCA9IFRSVUUsICMgYXZvaWQgb3ZlcmxhcHBpbmcgdGV4dCBwb2ludHMNCiAgICAgICAgICAgICBsYWJlbHNpemUgPSA1LCANCiAgICAgICAgICAgICBheGVzID0gYygxLDIpDQogICAgICAgICAgICApICsgDQogICAgDQogICAgdGhlbWUodGV4dCA9IGVsZW1lbnRfdGV4dChzaXplPTEwKSkgKyAjIE1ha2Ugb3VyIHRleHQgbGFyZ2VyDQoNCiAgICBzY2FsZV9zaXplKHJhbmdlID0gYygzLCAxMCkpICsgIyBVcGRhdGUgdGhlIHBvaW50IHNpemUgcmFuZ2UNCg0KICAgIHNjYWxlX2NvbG91cl92aXJpZGlzX2MoKSAjIENoYW5nZSB0aGUgY29sb3VyIHNjaGVtZQ0KDQp0ZXN0DQpgYGANCg0KYGBge3IsIGZpZy53aWR0aD0xNiwgZmlnLmhlaWdodD0xMn0NCiMgR3JhcGggb3VyIFVOc2NhbGVkIFBDQSBkYXRhLg0KDQpmdml6X3BjYV9pbmQoLi4uLCANCiAgICAgICAgICAgICBwb2ludHNpemUgPSBjb3ZpZF9kZW1vZ3JhcGhpY3Nfbm9ybS5teFssMV0sICMgU2V0IHBvaW50IHNpemUgYW5kIGNvbG91ciBieSBQSFUgcG9wdWxhdGlvbg0KICAgICAgICAgICAgIGNvbC5pbmQgPSBsb2cxMChjb3ZpZF9kZW1vZ3JhcGhpY3Nfbm9ybS5teFssMV0pLA0KICAgICAgICAgICAgIHJlcGVsID0gVFJVRSwgIyBhdm9pZCBvdmVybGFwcGluZyB0ZXh0IHBvaW50cw0KICAgICAgICAgICAgIGxhYmVsc2l6ZSA9IDUsIA0KICAgICAgICAgICAgIGF4ZXMgPSBjKDEsMikNCiAgICAgICAgICAgICkgKyANCiAgICANCiAgICB0aGVtZSh0ZXh0ID0gZWxlbWVudF90ZXh0KHNpemU9MTApKSArICMgTWFrZSBvdXIgdGV4dCBsYXJnZXINCg0KICAgIHNjYWxlX3NpemUocmFuZ2UgPSBjKDMsIDEwKSkgKyAjIFVwZGF0ZSB0aGUgcG9pbnQgc2l6ZSByYW5nZQ0KDQogICAgc2NhbGVfY29sb3VyX3ZpcmlkaXNfYygpICMgQ2hhbmdlIHRoZSBjb2xvdXIgc2NoZW1lDQpgYGANCg0KYGBge3J9DQojIFdoYXQgYXJlIHRoZSBjb250cmlidXRpb25zIG9mIGVhY2ggYWdlIGdyb3VwIHRvIHRoZSB2YXJpb3VzIHVuc2NhbGVkIGRpbWVuc2lvbnMNCnBodV91bnNjYWxlZC5wY2EkdmFyJGNvbnRyaWINCmBgYA0KDQpgYGB7cn0NCiMgTG9vayBhdCBvdXIgdW5zY2FsZWQgZWlnZW52YWx1ZXMgZGlyZWN0bHkNCnBodV91bnNjYWxlZC5wY2EkZWlnDQpgYGANCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjIyAzLjUuMSBTY2FsZWQgdnMgdW5zY2FsZWQgZGF0YQ0KDQpBcyB5b3UgY2FuIHNlZSBmcm9tIG91ciBncmFwaGluZyBvZiBib3RoIFBDQSBzZXRzLCB3aGVuIHdlIGNob29zZSBub3QgdG8gc2NhbGUgb3VyIGRhdGEgc29tZXRoaW5nIGEgbGl0dGxlIG1vcmUgZHJhc3RpYyBoYXBwZW5zLiBQQzEncyBwb3J0aW9uIG9mIHZhcmlhbmNlIGluY3JlYXNlcyB0byBhIDgzLjIlIGFjY291bnRpbmcgb2Ygb3ZlcmFsbCB2YXJpYW5jZS4gV2Ugc2VlIG1hbnkgb2Ygb3VyIHBvaW50cyBtb3ZlIGFuZCBzZXBhcmF0aW9uIGJldHdlZW4gc29tZSBvZiBvdXIgUEhVcyBpcyBpbmNyZWFzZWQgKEtpbmdzdG9uL0Zyb250ZW5hYyBhbmQgUGVlbCkuIFRoZXNlIGNoYW5nZXMgYXJlIGxpa2VseSBkdWUgdG8gdGhlIGxhcmdlciByYW5nZSBvZiB2YWx1ZXMgZnJvbSB0aGUgMjAgdG8gMzkgYWdlIGdyb3VwIHdoaWNoIG5vdyBjb250cmlidXRlcyB0byAzMSUgb2YgUEMxJ3MgdmFyaWF0aW9uLg0KDQpTbyB0aGluayBjYXJlZnVsbHkgYWJvdXQgeW91ciBmZWF0dXJlcyBhbmQgd2hhdCB0aGV5IHJlcHJlc2VudC4gRGVwZW5kaW5nIG9uIHRoZWlyIHJhbmdlLCBhbmQgdW5pdCB2YWx1ZXMsIHlvdSBtYXkgYmUgaW5hZHZlcnRlbnRseSB3ZWlnaGluZyB0aGVtIG1vcmUgdGhhbiB5b3VyIG90aGVyIGZlYXR1cmVzISBTY2FsaW5nIGFkanVzdHMgeW91ciBkYXRhIHNvIHRoYXQgYWxsIGZlYXR1cmVzIGFyZSB3ZWlnaHRlZCBlcXVhbGx5IQ0KDQojIyAzLjYuMCBDYW4gd2UgY2x1c3RlciBvdXIgdHJhbnNmb3JtZWQgZGF0YT8NCg0KTm93IHRoYXQgd2UndmUgZWZmZWN0aXZlbHkgcmVkdWNlZCB0aGUgY29tcGxleGl0eSBvZiBvdXIgZGF0YSwgY2FuIHdlIHByb2R1Y2UgdGhlIHNhbWUgay1tZWFucyBjbHVzdGVyaW5nIGFzIGJlZm9yZT8gTGV0J3MgdHJ5IHRvIGNsdXN0ZXIganVzdCBvbiB0aGUgdHdvIGRpbWVuc2lvbnMgcHJlc2VudGVkLCB3aGljaCB3ZSBoYXZlIGFscmVhZHkgZ3JhcGhlZCENCg0KYGBge3J9DQojIEhvdyBtYW55IGNsdXN0ZXJzIHNob3VsZCB3ZSB1c2UgYmFzZWQgb24gb3VyIFBDQSBkYXRhPw0KZnZpel9uYmNsdXN0KHBodV9zY2FsZWQucGNhJGluZCRjb29yZFssMToyXSwgRlVOY2x1c3RlciA9IGttZWFucywgbWV0aG9kPSJ3c3MiKQ0KZnZpel9uYmNsdXN0KHBodV9zY2FsZWQucGNhJGluZCRjb29yZFssMToyXSwgRlVOY2x1c3RlciA9IGttZWFucywgbWV0aG9kPSJzaWxob3VldHRlIikNCmBgYA0KDQpgYGB7cn0NCiMgR2VuZXJhdGUgb3VyIGstbWVhbnMgYW5hbHlzaXMgYW5kIHZpc3VhbGl6ZSBpdA0KDQpzZXQuc2VlZCgxMjMpDQoNCnBodV9QQ0Ffa21lYW5zLnBsb3QgPC0NCg0KICAgIGttZWFucyguLi4sIGNlbnRlcnMgPSAzKSAlPiUgDQoNCiAgICBmdml6X2NsdXN0ZXIoLiwgIyBvdXIgay1tZWFucyBvYmplY3QNCiAgICAgICAgICAgICAgICAgZGF0YSA9IC4uLiwgIyBPdXIgb3JpZ2luYWwgZGF0YSBuZWVkZWQgZm9yIFBDQSB0byB2aXN1YWxpemUNCiAgICAgICAgICAgICAgICAgZWxsaXBzZS50eXBlID0gImNvbnZleCIsIA0KICAgICAgICAgICAgICAgICBnZ3RoZW1lID0gdGhlbWVfYncoKSwgDQogICAgICAgICAgICAgICAgIHJlcGVsPVRSVUUsICMgVHJ5IHRvIGF2b2lkIG92ZXJsYXBwaW5nIHRleHQNCiAgICAgICAgICAgICAgICAgbGFiZWxzaXplID0gMjAsDQogICAgICAgICAgICAgICAgIHBvaW50c2l6ZSA9IDQsDQogICAgICAgICAgICAgICAgIG1haW4gPSAiSy1tZWFucyBjbHVzdGVyaW5nIG9mIFBIVSBieSBub3JtYWxpemVkIGNhc2UgbnVtYmVyIEFGVEVSIFByaW5jaXBhbCBDb21wb25lbnQgQW5hbHlzaXMiDQogICAgICAgICAgICAgICAgICkgKw0KDQogICAgIyBTZXQgc29tZSBnZ3Bsb3QgdGhlbWUgaW5mb3JtYXRpb24NCiAgICB0aGVtZSh0ZXh0ID0gZWxlbWVudF90ZXh0KHNpemU9MjApKSArDQoNCiAgICAjIFNldCB0aGUgY29sb3VyIGFuZCBmaWxsIHNjaGVtZSB0byB2aXJpZGlzDQogICAgc2NhbGVfZmlsbF92aXJpZGlzX2QoKSArDQogICAgc2NhbGVfY29sb3VyX3ZpcmlkaXNfZCgpDQoNCiMgcGh1X3BjYV9rbWVhbnMucGxvdA0KYGBgDQoNCmBgYHtyLCBmaWcud2lkdGg9MTYsIGZpZy5oZWlnaHQ9MTJ9DQojIFBsb3QgYm90aCBvZiBvdXIgZmlndXJlcyB0b2dldGhlcg0KZmlnLnNob3c9ImhvbGQiOyBvdXQud2lkdGg9IjUwJSI7IG91dC5oZWlnaHQgPSAiNTAlIg0KcGh1X2ttZWFucy5wbG90DQpwaHVfUENBX2ttZWFucy5wbG90DQpgYGANCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjIyAzLjYuMSBXYXMgaXQgd29ydGggaXQgdG8gdXNlIFBDQT8NCg0KRnJvbSBvdXIgZXN0aW1hdGlvbnMsIHRoZSBjbHVzdGVyaW5nIGNob2ljZXMgbG9vayBuZWFyIGlkZW50aWNhbCBhbmQgdGhpcyBpcyBub3QgZXhhY3RseSB0aGUgYmVzdCBkYXRhc2V0IHRvIHdvcmsgd2l0aCBzaW5jZSBpdCBpcyByYXRoZXIgc21hbGwgYnV0IHlvdSBjYW4gc2VlIHRoYXQgd2UgaGF2ZSBub3cgdHJhbnNmb3JtZWQgb3VyIGRhdGEgaW50byBqdXN0IDIgZmVhdHVyZXMhIENvdWxkIHdlIGRvIHRoZSBzYW1lIHVzaW5nIGp1c3QgMiBmZWF0dXJlcyBmcm9tIG91ciBvcmlnaW5hbCBkYXRhc2V0PyBObyEgU28gaW1hZ2luZSB0cmFuc2Zvcm1pbmcgYSBtdWNoIGxhcmdlciBhbmQgZmVhdHVyZS1oZWF2eSBkYXRhc2V0IGludG8gYSBzbWFsbGVyIG51bWJlciBvZiBmZWF0dXJlcy4gRnJvbSB0aGUgZGF0YSwgd2UgYXJlIGFsc28gYWJsZSB0byBkaXNjZXJuIHdoaWNoIG9mIG91ciBvcmlnaW5hbCBmZWF0dXJlcyBtYXkgcmVhbGx5IGNvbnRyaWJ1dGUgdG8gZWFjaCBuZXcgcHJpbmNpcGFsIGNvbXBvbmVudCENCg0KU29tZXRoaW5nIHRvIGNvbnNpZGVyIHRoZSBuZXh0IHRpbWUgeW91J3JlIHdvcmtpbmcgd2l0aCBhIGxhcmdlIGRhdGEgc2V0IQ0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMgMy43LjAgUENBIG9uIGEgbGFyZ2UgUk5BLVNlcSBkYXRhc2V0DQoNCk5vdyB0aGF0IHdlJ3ZlIHdhbGtlZCB0aHJvdWdoIGhvdyB0byBnZW5lcmF0ZSBhIFBDQSBmb3IgYSBzaW1wbGVyIGRhdGFzZXQsIGxldCdzIHJldHVybiB0byBvdXIgUk5BLVNlcSByZWFkY291bnQgZGF0YS4gV2Ugd29uJ3QgdHJpbSBkb3duIHRoZSBkYXRhIGF0IGFsbCBidXQgcmF0aGVyIGp1c3QgcHJvdmlkZSB0aGUgZW50aXJlIGRhdGFzZXQgYXMgYSBtYXRyaXggZm9yIGZlYXR1cmUgcmVkdWN0aW9uLiBMZXQncyByZXZpZXcgdGhlIHN0ZXBzOg0KDQoxLiAgR2VuZXJhdGUgYSBtYXRyaXggb2YgeW91ciBkYXRhLCBuYW1pbmcgdGhlIGNvbHVtbnMgYW5kIHJvd3MgYnkgd2hhdGV2ZXIgaW5mb3JtYXRpb24geW91IG1pZ2h0IGhhdmUuIE1ha2Ugc3VyZSB5b3VyIGNvbHVtbnMgcmVwcmVzZW50IHlvdXIgZmVhdHVyZXMsIGFuZCByb3dzIGFyZSBvYnNlcnZhdGlvbnMuDQoyLiAgQ3JlYXRlIHRoZSBQQ0Egb2JqZWN0Lg0KMy4gIFJldmlldyB5b3VyIGVpZ2VudmFsdWVzIGFuZCBlaWdlbnZlY3RvcnMgdG8gYXNzZXNzIGhvdyB3ZWxsIHlvdXIgcmVkdWN0aW9uIHdvcmtlZC4gRGV0ZXJtaW5lIGhvdyBtYW55IGRpbWVuc2lvbnMgeW91IHdpc2ggdG8gdXNlIGluIGZ1cnRoZXIgYW5hbHlzaXMuDQo0LiAgQ2FsY3VsYXRlIHRoZSBudW1iZXIgb2YgcG9zc2libGUgY2x1c3RlcnMuDQo1LiAgUGxvdCB0aGUgcmVzdWx0cy4NCg0KYGBge3J9DQojIDEuIEdlbmVyYXRlIG91ciBtYXRyaXggZnJvbSB0aGUgcmVhZGNvdW50IGRhdGENCnd5bGVyX3JlYWRjb3VudHNfYWxsLm14IDwtDQoNCiAgd3lsZXJfcmVhZGNvdW50cy5kZiAlPiUgDQogICMgUmVuYW1lIHRoZSBjb2x1bW5zIGJ5IHJlbW92aW5nIHRoZSBmaXJzdCBwb3J0aW9uOiBBRUNJSV94eA0KICByZW5hbWVfd2l0aCguLCB+IHN0cl9yZXBsYWNlKHN0cmluZyA9IC54LCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXR0ZXJuID0gciIoXHcqX1xkKl8pIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVwbGFjZSA9ICIiKSkgJT4lIA0KDQogICMgS2VlcCBvbmx5IHRoZSBleHBlcmltZW50YWwgb2JzZXJ2YXRpb25zIChob3cgbWFueSBhcmUgdGhlcmU/KQ0KICBkcGx5cjo6c2VsZWN0KGMoMzozOCkpICU+JSANCg0KICAjIENvbnZlcnQgdG8gYSBtYXRyaXgNCiAgYXMubWF0cml4KCkgJT4lIA0KDQogICMgVHJhbnNwb3NlIHRoZSBtYXRyaXggc28gb3VyIGNvbHVtbnMgYXJlIG5vdyBnZW5lcw0KICAuLi4NCg0KIyBOYW1lIHRoZSByb3dzIG9mIHRoZSBtYXRyaXgNCmNvbG5hbWVzKHd5bGVyX3JlYWRjb3VudHNfYWxsLm14KSA8LSB3eWxlcl9yZWFkY291bnRzLmRmJGdlbmUNCg0KIyBXZSdsbCBwZWVrIGF0IHRoZSBzdHJ1Y3R1cmUgb2YgdGhlIG1hdHJpeC4gTG9va2luZyBhdCBpdCB3aXRoIGhlYWQoKSB3aWxsIGJlIG1lc3N5Lg0Kc3RyKHd5bGVyX3JlYWRjb3VudHNfYWxsLm14KQ0KYGBgDQoNCmBgYHtyfQ0KIyAyLiBHZW5lcmF0ZSB0aGUgUENBIG9iamVjdA0KcmVhZGNvdW50c19zY2FsZWQucGNhIDwtIFBDQSguLi4sIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzY2FsZS51bml0ID0gVFJVRSwgIyBXaGF0IGhhcHBlbnMgd2hlbiB3ZSBkb24ndCBzY2FsZSB0aGUgZGF0YT8NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbmNwID0gNDAsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdyYXBoID0gVFJVRSkNCmBgYA0KDQpgYGB7cn0NCiMgMy4gUmV2aWV3IGhvdyBtdWNoIHZhcmlhbmNlIGlzIGV4cGxhaW5lZCBieSB0aGUgbmV3IGNvbXBvbmVudHMNCnJlYWRjb3VudHNfc2NhbGVkLnBjYSRlaWcNCmBgYA0KDQpgYGB7cn0NCiMgNC4gSG93IG1hbnkgY2x1c3RlcnMgc2hvdWxkIHdlIHVzZSBiYXNlZCBvbiBvdXIgUENBIGRhdGE/DQpmdml6X25iY2x1c3QocmVhZGNvdW50c19zY2FsZWQucGNhJGluZCRjb29yZFssMToyXSwgRlVOY2x1c3RlciA9IGttZWFucywgbWV0aG9kPSJzaWxob3VldHRlIikNCmBgYA0KDQpgYGB7ciwgZmlnLndpZHRoPTE2LCBmaWcuaGVpZ2h0PTEyfQ0KIyA1LiBHZW5lcmF0ZSBvdXIgay1tZWFucyBhbmFseXNpcyBhbmQgdmlzdWFsaXplIGl0DQpzZXQuc2VlZCgxMjMpDQoNCnJlYWRjb3VudHNfUENBX2ttZWFucy5wbG90IDwtDQoNCiAgIyBMZXQncyBnbyB3aXRoIDQgY2x1c3RlcnMgaW4gb3JkZXIgdG8gY29tcGFyZSB0byBvdXIgb3JpZ2luYWwgdmVyc2lvbi4NCiAgIyBUaGlzIGlzIGNsb3NlIHRvIHRoZSBzaWxob3VldHRlIHZhbHVlIGFzIHdlbGwuDQogIGttZWFucyhyZWFkY291bnRzX3NjYWxlZC5wY2EkaW5kJGNvb3JkWywxOjZdLCBjZW50ZXJzID0gLi4uLCApICU+JSANCg0KICBmdml6X2NsdXN0ZXIoLiwgIyBvdXIgay1tZWFucyBvYmplY3QNCiAgICAgICAgICAgICAgIGRhdGEgPSByZWFkY291bnRzX3NjYWxlZC5wY2EkaW5kJGNvb3JkWywxOjJdLCAjIE91ciBvcmlnaW5hbCBkYXRhIG5lZWRlZCBmb3IgUENBIHRvIHZpc3VhbGl6ZQ0KICAgICAgICAgICAgICAgZWxsaXBzZS50eXBlID0gImNvbnZleCIsIA0KICAgICAgICAgICAgICAgZ2d0aGVtZSA9IHRoZW1lX2J3KCksIA0KICAgICAgICAgICAgICAgcmVwZWw9VFJVRSwgIyBUcnkgdG8gYXZvaWQgb3ZlcmxhcHBpbmcgdGV4dA0KICAgICAgICAgICAgICAgbGFiZWxzaXplID0gMjAsDQogICAgICAgICAgICAgICBwb2ludHNpemUgPSA0LA0KICAgICAgICAgICAgICAgbWFpbiA9ICJLLW1lYW5zIGNsdXN0ZXJpbmcgb2YgUEhVIGJ5IG5vcm1hbGl6ZWQgY2FzZSBudW1iZXIgQUZURVIgUHJpbmNpcGFsIENvbXBvbmVudCBBbmFseXNpcyINCiAgICAgICAgICAgICAgICkgKw0KDQogICMgU2V0IHNvbWUgZ2dwbG90IHRoZW1lIGluZm9ybWF0aW9uDQogIHRoZW1lKHRleHQgPSBlbGVtZW50X3RleHQoc2l6ZT0xMCkpICsNCg0KICAjIFNldCB0aGUgY29sb3VyIGFuZCBmaWxsIHNjaGVtZSB0byB2aXJpZGlzDQogIHNjYWxlX2ZpbGxfdmlyaWRpc19kKCkgKw0KICBzY2FsZV9jb2xvdXJfdmlyaWRpc19kKCkNCg0KIyBMb29rIGF0IG91ciBvcmlnaW5hbCBwbG90IHdpdGggNCBjZW50cmVzDQp3eWxlcl9rbWVhbnMucGxvdA0KDQojIFZlcnN1cyBvdXIgUENBIHdpdGggNCBjZW50cmVzDQpyZWFkY291bnRzX1BDQV9rbWVhbnMucGxvdA0KYGBgDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQpJZiB3ZSB3ZXJlIHRvIGNvbXBhcmUgb3VyIHR3byB2ZXJzaW9ucyBvZiB0aGUgay1tZWFucyBjbHVzdGVyaW5nIHVzaW5nIHJhdyBkYXRhIHZzIG91ciBQQ0EgZGltZW5zaW9uLXJlZHVjZWQgZGF0YSwgdGhlIHJlc3VsdHMgYXJlIHByZXR0eSBzaW1pbGFyIGFsdGhvdWdoIG5vdCBxdWl0ZS4gSSd2ZSBhbHNvIGFkZGVkIHdoYXQgeW91ciBkYXRhIHdvdWxkIGxvb2sgbGlrZSBpZiB5b3UgZGlkIE5PVCBzY2FsZSB0aGUgZGF0YSBiZWZvcmUgcGVyZm9ybWluZyBQQ0EuDQoNCistLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0rLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0rLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0rLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0rLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0rDQp8IEFzcGVjdCAgICAgICAgICAgICAgICAgICAgfCBSYXcgSy1tZWFucyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfCBQQ0EgSy1tZWFucyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfCBQQ0EgSy1tZWFucyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfCBQQ0EgSy1tZWFucyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KKzo9PT09PT09PT09PT09PT09PT09PT09PT09Ois6PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09Ois6PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09Ois6PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09Ois6PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09OisNCnwgU2NhbGVkICAgICAgICAgICAgICAgICAgICB8IFNjYWxlIGF0IHRpbWUgb2Ygay1tZWFucyAgICAgICAgICAgICAgICAgICB8IFNjYWxlIGF0IHRpbWUgb2YgUENBICAgICAgICAgICAgICAgICAgICB8IFNjYWxlIGF0IHRpbWUgb2YgUENBICAgICAgICAgICAgICAgICAgICAgICB8IFVuc2NhbGVkIGF0IHRpbWUgb2YgUENBICAgICAgICAgICAgICAgICAgICB8DQorLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKw0KfCBGZWF0dXJlcyB1c2VkIGZvciBrLW1lYW5zIHwgMjdLIGdlbmVzICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwgVG9wIDIgY29tcG9uZW50cyAoNDMlIHZhcikgICAgICAgICAgICAgIHwgVG9wIDYgY29tcG9uZW50cyAoNjclIHZhcikgICAgICAgICAgICAgICAgIHwgVG9wIDIgY29tcG9uZW50cyAoNzMgJSB2YXIpICAgICAgICAgICAgICAgIHwNCistLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0rLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0rLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0rLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0rLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0rDQp8IERpbSAxICAgICAgICAgICAgICAgICAgICAgfCA0NC42JSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfCAyNi4xJSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfCAyNi4xJSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfCA1MC4zJSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KKy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSstLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSstLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSstLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSstLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSsNCnwgRGltIDIgICAgICAgICAgICAgICAgICAgICB8IDE3LjglICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8IDE2LjklICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8IDE2LjklICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8IDIyLjclICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQorLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKw0KfCBDbHVzdGVyIDEgICAgICAgICAgICAgICAgIHwgMjRoOiBhbGwsIERNU08tdHJlYXRlZDsgICAgICAgICAgICAgICAgICAgIHwgKioyNGgrNDhoOiBTQVJTLUNvVjIgaW5mZWN0aW9uLCBETVNPOyoqIHwgMjRoOiBhbGwsIERNU08tdHJlYXRlZDsgICAgICAgICAgICAgICAgICAgIHwgMjRoOiBhbGwsIERNU08tdHJlYXRlZDsgICAgICAgICAgICAgICAgICAgIHwNCnwgICAgICAgICAgICAgICAgICAgICAgICAgICB8ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQp8ICAgICAgICAgICAgICAgICAgICAgICAgICAgfCA0OGgrNzJoOiB1bmluZmVjdGVkLCBETVNPICAgICAgICAgICAgICAgICAgfCAqKlNvbWUgMjRoKzQ4aDogdW5pbmZlY3RlZCwgRE1TTyoqICAgICAgfCA0OGgrNzJoOiB1bmluZmVjdGVkLCBETVNPICAgICAgICAgICAgICAgICAgfCA0OGgrNzJoOiB1bmluZmVjdGVkLCBETVNPICAgICAgICAgICAgICAgICAgfA0KKy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSstLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSstLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSstLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSstLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSsNCnwgQ2x1c3RlciAyICAgICAgICAgICAgICAgICB8IDQ4aCs3Mmg6IFNBUlMtQ29WMiBpbmZlY3Rpb24sIERNU08tdHJlYXRlZCB8ICoqNDhoOiB1bmluZmVjdGVkLCBETVNPOyoqICAgICAgICAgICAgICB8IDQ4aCs3Mmg6IFNBUlMtQ29WMiBpbmZlY3Rpb24sIERNU08tdHJlYXRlZCB8IDQ4aCs3Mmg6IFNBUlMtQ29WMiBpbmZlY3Rpb24sIERNU08tdHJlYXRlZCB8DQp8ICAgICAgICAgICAgICAgICAgICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KfCAgICAgICAgICAgICAgICAgICAgICAgICAgIHwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwgKio3Mmg6IGFsbCwgRE1TTy10cmVhdGVkKiogICAgICAgICAgICAgIHwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwNCistLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0rLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0rLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0rLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0rLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0rDQp8IENsdXN0ZXIgMyAgICAgICAgICAgICAgICAgfCAyNGg6IDE3QUFHLXRyZWF0ZWQgICAgICAgICAgICAgICAgICAgICAgICAgfCAyNGg6IDE3QUFHLXRyZWF0ZWQgICAgICAgICAgICAgICAgICAgICAgfCAyNGg6IDE3QUFHLXRyZWF0ZWQgICAgICAgICAgICAgICAgICAgICAgICAgfCAyNGg6IDE3QUFHLXRyZWF0ZWQgICAgICAgICAgICAgICAgICAgICAgICAgfA0KKy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSstLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSstLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSstLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSstLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSsNCnwgQ2x1c3RlciA0ICAgICAgICAgICAgICAgICB8IDQ4aCs3Mmg6IDE3QUFHLXRyZWF0ZWQgICAgICAgICAgICAgICAgICAgICB8IDQ4aCs3Mmg6IDE3QUFHLXRyZWF0ZWQgICAgICAgICAgICAgICAgICB8IDQ4aCs3Mmg6IDE3QUFHLXRyZWF0ZWQgICAgICAgICAgICAgICAgICAgICB8IDQ4aCs3Mmg6IDE3QUFHLXRyZWF0ZWQgICAgICAgICAgICAgICAgICAgICB8DQorLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKw0KDQpTb21lIGludGVyZXN0aW5nIGNvbmNsdXNpb25zIHdlIGNhbiBkcmF3IGZyb20gdGhpcyBhcmUNCg0KMS4gIFdoZXRoZXIgb3VyIGRhdGEgaXMgc2NhbGVkIGp1c3QgcHJpb3IgdG8gcGVyZm9ybWluZyBrLW1lYW5zLCBzY2FsZWQgb3IgdW5zY2FsZWQgd2l0aCBQQ0EsIHdlIHNlZSB0aGF0IG91ciAxN0FBRy10cmVhdGVkIHNhbXBsZXMgaGF2ZSAyIG1haW4gY2x1c3RlcnMgcmVnYXJkbGVzcyBvZiBpbmZlY3Rpb24gc3RhdHVzLiBFaXRoZXIgMjRoIG9yIDQ4aCs3MmggZGF0YS4gVGhlcmUgaXMgbGlrZWx5IGEgc3Ryb25nIGVmZmVjdCBvbiBleHByZXNzaW9uIGJ5IHRoZSAqKioxN0FBRyB0cmVhdG1lbnQqKiogdGhhdCBpcyBtdWNoIHN0cm9uZ2VyIHRoYW4gdGhlIGluZmVjdGlvbiBieSBTQVJTLUNvVjIuIEluIGZhY3QsIGlmIHdlIHdlcmUgdG8gZXhwbG9yZSB0aGlzIGZ1cnRoZXIgd2Ugd291bGQgc2VlIHRoYXQgaWYgd2UgY3JlYXRlZCBtb3JlIGNsdXN0ZXJzLCB3ZSB3b3VsZCBmaW5kIHRoYXQgKioqdGltZSoqKiBwbGF5cyBhIGJpZ2dlciByb2xlIGluIGZ1cnRoZXIgc2VwYXJhdGluZyB0aGVzZSBncm91cHMgdGhhbiBTQVJTLUNvVjIgaW5mZWN0aW9uLg0KMi4gIENob29zaW5nIHRoZSBudW1iZXIgb2YgZmVhdHVyZXMgZG9lcyBtYXR0ZXIgZXZlbiBhZnRlciBQQ0EhIExvb2tpbmcgYXQganVzdCBvdXIgc2NhbGVkIFBDQSBkYXRhIHRvIGNyZWF0ZSBhIGstbWVhbiBjbHVzdGVyaW5nLCB3ZSBjYW4ndCBnZXQgZ29vZCBjb25jb3JkYW5jZSB3aXRoIG91ciByYXcgZGF0YSBhbmFseXNpcyB1c2luZyBvbmx5IG91ciAqKip0b3AgdHdvIFBDQSBmZWF0dXJlcyoqKiB3aGljaCBhY2NvdW50IGZvciBqdXN0ICoqKjQzJSBvZiB2YXJpYW5jZSoqKi4gT24gdGhlIG90aGVyIGhhbmQsIGlmIHdlIGNob29zZSBvdXIgKioqdG9wIHNpeCBmZWF0dXJlcyoqKiBmb3Igay1tZWFucyBjbHVzdGVyaW5nIHdoaWNoIGFjY291bnQgZm9yICoqKjY3JSBvZiB2YXJpYW5jZSoqKiwgd2UgZG8gZ2V0IHRoZSBzYW1lIGNsdXN0ZXJzIHJldHVybmVkLCBhbGJlaXQgYSBzbGlnaGx0eSBkaWZmZXJlbnQgbG9va2luZyBwcm9qZWN0Lg0KDQpUaGVyZWZvcmUgUENBIGNhbiBoZWxwIGlkZW50aWZ5IGZhY3RvcnMgd2hpY2ggaGVhdmlseSBpbmZsdWVuY2UgeW91ciBzYW1wbGVzIGFzIGxvbmcgYXMgeW91IGFsc28gY29ycmVjdGx5IGNob29zZSBlbm91Z2ggZmVhdHVyZXMgdG8gcmVwcmVzZW50IHlvdXIgdHJhbnNmb3JtZWQgZGF0YS4NCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjIDMuOC4wIFJlc3RhcnQgUiENCg0KQXQgdGhpcyBwb2ludCwgd2UndmUgYWNjdW11bGF0ZWQgYSBsb3Qgb2YgZGF0YSBpbiBtZW1vcnkuIFdlIHdvbid0IG5lZWQgb3VyIHByZXZpb3VzIGRhdGEgYW55bW9yZSBzbyBsZXQncyByZXN0YXJ0IFIgYW5kIHJlbG9hZCB0aGUgbGlicmFyaWVzIHdlJ2xsIG5lZWQuIFlvdSBjYW4NCg0KYGBge3J9DQojIFBhY2thZ2VzIHRvIGhlbHAgdGlkeSBvdXIgZGF0YQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KHJlYWR4bCkNCmxpYnJhcnkobWFncml0dHIpDQoNCiMgUGFja2FnZXMgZm9yIHRoZSBncmFwaGljYWwgYW5hbHlzaXMgc2VjdGlvbg0KbGlicmFyeSh2aXJpZGlzKQ0KbGlicmFyeShSQ29sb3JCcmV3ZXIpDQoNCiMgRGF0YSBwcm9qZWN0aW9uIHBhY2thZ2VzDQpsaWJyYXJ5KFJ0c25lKQ0KbGlicmFyeSh1bWFwKQ0KDQpgYGANCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMgNC4wLjAgTm9uLWxpbmVhciBwcm9qZWN0aW9uDQoNCjo6OiB7YWxpZ249ImNlbnRlciJ9DQo8aW1nIHNyYz0iaHR0cHM6Ly9naXRodWIuY29tL2NhbW9rL0NTQl9Db3Vyc2VfTWF0ZXJpYWxzL2Jsb2IvbWFpbi9BZHZWaXovdC1TTkVfVU1BUF9leGFtcGxlcy5wbmc/cmF3PXRydWUiIHdpZHRoPSI5MDAiLz4NCg0KSG93IGRvIHdlIGlkZW50aWZ5IHRyZW5kcyBvciBncm91cHMgd2l0aGluIGRlZXBseSBjb21wbGV4IGRhdGEgaW4gYW4gdW5zdXBlcnZpc2VkIG1hbm5lcj8NCjo6Og0KDQpTbyB3ZSBqdXN0IHNwZW50IG1vc3Qgb2Ygb3VyIHRpbWUgdHJ5aW5nIHRvIHVuZGVyc3RhbmQgaG93IHRvIHRyYW5zZm9ybSBvdXIgc2V0cyBiYXNlZCBvbiB0aGUgbGFyZ2UgYW1vdW50cyBvZiB2YXJpYXRpb24gaW4gb3VyIGRhdGEuIFRoZXJlIGFyZSBzb21lIGxpbWl0YXRpb25zIHdlJ3ZlIGRpc2N1c3NlZCBidXQgaW4gc29tZSBjYXNlcywgZGVwZW5kaW5nIG9uIHlvdXIgZGF0YXNldCBzaXplIHlvdSBtYXkgYmUgaW50ZXJlc3RlZCBpbiBmaW5kaW5nIHNtYWxsLCBsb2NhbCBzaW1pbGFyaXRpZXMgcmF0aGVyIHRoYW4gdGhlIGxhcmdlIHZhcmlhdGlvbiB0aGF0IGNvbWVzIHdpdGggUENBLg0KDQpUaGVyZSBhcmUgdHdvIHBvcHVsYXIgcHJvamVjdGlvbnMgd2UnbGwgZGlzY3VzcyB0b2RheSBidXQgZmlyc3QgbGV0J3MgcHV0IHRvZ2V0aGVyIGEgbW9yZSBjb21wbGV4IGRhdGFzZXQgYmFzZWQgb24gc29tZSBSTkFzZXEgZGF0YSBpbiBgR1NFMTUwMzE2X0Rlc2VxTm9ybUNvdW50c19maW5hbC50eHRgIGFuZCBpdCdzIGNvbXBhbmlvbiBmaWxlIGAyMDIwLjA3LjMwLjIwMTY1MjQxLXN1cHBfdGFibGVzLnhsc3hgDQoNCmBgYHtyfQ0KIyBSZWFkIGluIG91ciBSTkFzZXEgZGF0YQ0KdGlzc3VlX2RhdGEuZGYgPC0gLi4uKGZpbGUgPSAuLi4sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhlYWRlciA9IFRSVUUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJvdy5uYW1lcyA9IDEpDQojIFRha2UgYSBxdWljayBsb29rIGF0IGl0DQpoZWFkKHRpc3N1ZV9kYXRhLmRmKQ0KZGltKHRpc3N1ZV9kYXRhLmRmKQ0KDQojIFJlYWQgaW4gc29tZSBhZGRpdGlvbmFsIHBhdGllbnQgZGF0YQ0KcGF0aWVudF9kYXRhLmRmIDwtIHJlYWRfZXhjZWwoIi4vZGF0YS8yMDIwLjA3LjMwLjIwMTY1MjQxLXN1cHBfdGFibGVzLnhsc3giLCBzaGVldD0yKQ0KDQojIFRha2UgYSBxdWljayBsb29rIGF0IGl0DQpoZWFkKHBhdGllbnRfZGF0YS5kZikNCmRpbShwYXRpZW50X2RhdGEuZGYpDQpgYGANCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjIyA0LjAuMSBXaHkgY2FuJ3Qgd2UgdXNlIFBDQSBvbiB0aGlzIGRhdGFzZXQ/DQoNCkJlZm9yZSB3ZSBhdHRlbXB0IG9uZSBvZiBvdXIgbm9uLWxpbmVhciBwcm9qZWN0aW9uIG1ldGhvZHMsIGxldCdzIHRyeSBhbmQgdXNlIHNvbWUgb2YgdGhlIG1ldGhvZHMgd2UndmUgc3BlbnQgYWxsIHRoaXMgdGltZSBsb29raW5nIGF0LiBXZSdsbCBuYWl2ZWx5IHJlcGVhdCBvdXIgUENBIHN0ZXBzIG9uIHRoaXMgdGlzc3VlIGRhdGEgYW5kIHNlZSBpZiB3ZSBjYW4gcHVsbCBhbnkgaW5mb3JtYXRpb24gZnJvbSBpdC4gV2Uga25vdyB0aGVyZSBhcmUgMTAgdGlzc3VlIGdyb3VwcyBpbiBvdXIgZGF0YSBzbyB3ZSdsbCB1c2UgdGhhdCBhcyBhIGJhc2lzIGZvciBjbHVzdGVyaW5nLg0KDQo6Ojoge2FsaWduPSJjZW50ZXIifQ0KPGltZyBzcmM9Imh0dHBzOi8vZ2l0aHViLmNvbS9jYW1vay9DU0JfQ291cnNlX01hdGVyaWFscy9ibG9iL21haW4vQWR2Vml6L3Rpc3N1ZUluZmVjdGlvbi5QQ0Eua21lYW5zLnBuZz9yYXc9dHJ1ZSIgd2lkdGg9IjcwMCIvPg0KOjo6DQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQpBcyB3ZSBjYW4gc2VlIGZyb20gdGhlIGFib3ZlIHBsb3QsIGEgbWFqb3JpdHkgb2Ygb3VyIHNhbXBsZXMgZnJvbSB2YXJpb3VzIHRpc3N1ZSB0eXBlcyBmYWxsIGludG8gYSBzaW5nbGUgY2x1c3Rlci4gV2hhdCdzIG1vcmUsIGFzIHdlJ2xsIHNlZSwgdGhlcmUgaXMgc29tZSBzdHJ1Y3R1cmUgaW4gdGhlc2Ugc2FtcGxlcyBhcyBtdWx0aXBsZSBzYW1wbGVzIGNvbWUgZnJvbSB0aGUgc2FtZSBwYXRpZW50LiBTb21ldGltZXMgZXZlbiB0aGUgc2FtZSB0aXNzdWUhIFRoZXNlIHJlbGF0aW9uc2hpcHMgbWF5IGhhdmUgYSBsYXJnZSBlZmZlY3QgaW4gdGhlIG92ZXJhbGwgdmFyaWF0aW9uIHdlIHNlZSBpbiB0aGUgZGF0YSwgbWFraW5nIGl0IGhhcmQgdG8gZGlzdGluZ3Vpc2ggYmV0d2VlbiBldmVuIHRpc3N1ZSB0eXBlcy4gVGhpcyBpcyB3aHkgd2UnbGwgdHVybiB0byBub24tbGluZWFyIHByb2plY3Rpb24gYW5kIHNlZSBpZiBpdCBjYW4gaGVscCB1cyBvdXQuIEJlZm9yZSB0aGF0IHdlJ2xsIG5lZWQgdG8gZG8gc29tZSBtb3JlIHdyYW5nbGluZy4uLg0KDQojIyMgNC4wLjIgUmVmb3JtYXQgb3VyIHBhdGllbnQgbWV0YSBkYXRhDQoNCkZpcnN0IGxldCdzIGV4YW1pbmUgb3VyIGBwYXRpZW50X2RhdGEuZGZgIHdoaWNoIGhhcyAyNCBwYXRpZW50IHNhbXBsZXMgKGFrYSBjYXNlcykgbGlzdGVkIGluIHRvdGFsLiBUaGVzZSAyNCBjYXNlcyByZWxhdGUgYmFjayB0byBgdGlzc3VlX2RhdGEuZGZgIHdoaWNoIGhhdmUgdmFyaWFibGVzIHJlcHJlc2VudGluZyBkaWZmZXJlbnQgY29tYmluYXRpb25zIG9mIGNhc2UgYW5kIHRpc3N1ZSB0eXBlIGFuZCBvYnNlcnZhdGlvbnMgZm9yIHNvbWUgNTlLIHRyYW5zY3JpcHRzLg0KDQpBdCB0aGlzIHBvaW50IHdlIHdhbnQgdG8gcmVmb3JtYXQgdGhlIGNvbHVtbiBuYW1lcyBpbiBgcGF0aWVudF9kYXRhLmRmYCBhIGJpdCBiZWZvcmUgc2VsZWN0aW5nIGZvciBqdXN0IHRoZSB2aXJhbCBsb2FkIGFuZCB2aXJhbCBsb2FkIHBlcmNlbnRhZ2UgaW5mb3JtYXRpb24gbG9jYXRlZCBpbiB0aGUgMm5kIGFuZCAzcmQgY29sdW1uLiBXZSdsbCBob2xkIG9udG8gdGhpcyBpbiBgcGF0aWVudF92aXJhbF9sb2FkLmRmYCBmb3IgbGF0ZXIgdXNlLg0KDQpgYGB7cn0NCiMgQ3JlYXRlIGEgZGF0YWZyYW1lIGhvbGRpbmcganVzdCB0aGUgdmlyYWxfbG9hZCBpbmZvcm1hdGlvbiBmb3IgZWFjaCBwYXRpZW50DQpwYXRpZW50X3ZpcmFsX2xvYWQuZGYgPC0NCiAgcGF0aWVudF9kYXRhLmRmICU+JSANCg0KICAjIFJldGFpbiBqdXN0IHRoZSBmaXJzdCAzIGNvbHVtbnMNCiAgZHBseXI6OnNlbGVjdCgxOjMpICU+JSANCg0KICAjIFJlbmFtZSB0aGUgMm5kIGFuZCAzcmQgY29sdW1ucw0KICAuLi4oY2FzZV9udW0gPSAxLCB2aXJhbF9sb2FkID0gMiwgdmlyYWxfbG9hZF9wZXJjZW50ID0gMykNCg0KaGVhZChwYXRpZW50X3ZpcmFsX2xvYWQuZGYpDQpgYGANCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjIyA0LjAuMyBQcmVwYXJlIG91ciBSTkFzZXEgdGlzc3VlIGRhdGEgZm9yIGFuYWx5c2lzIGJ5IGVsaW1pbmF0aW5nIGZlYXR1cmVzDQoNCldlIG5vdyB3YW50IHRvIGZvcm1hdCBgdGlzc3VlX2RhdGEuZGZgIG11Y2ggaW4gdGhlIHdheSB3ZSBkaWQgd2l0aCBvdXIgUEhVIGRhdGEuIFdlIHdhbnQgdG8gY29udmVydCBvdXIgY3VycmVudCBkYXRhIHdoaWNoIGxpc3RzIGdlbmVzIGFzIG9ic2VydmF0aW9ucyBhbmQgdGlzc3VlIHNhbXBsZXMgYXMgY29sdW1ucy4gRXNzZW50aWFsbHksIHdlJ2QgbGlrZSB0byB0cmFuc3Bvc2UgdGhpcyBhbmQgd2UgKmNvdWxkKiBkbyB0aGF0IGJ1dCB0aGUgdHJhbnNwb3NpdGlvbiBjb252ZXJ0cyBldmVyeXRoaW5nIHRvIGEgbWF0cml4LiBJbiB0aGUgZW5kLCB3ZSB3YW50IHRvIHdvcmsgd2l0aCBhIGRhdGEgZnJhbWUgc28gd2UgY2FuIGhvbGQgbW9yZSBpbmZvcm1hdGlvbi4NCg0KKioqVG8gcmVkdWNlIG9uIG1lbW9yeSBhbmQgcnVudGltZSoqKiwgaG93ZXZlciwgd2Ugc2hvdWxkIHRyaW0gb3VyIGRhdGFzZXQuIFdlIGFyZW4ndCByZWFsbHkgaW50ZXJlc3RlZCBpbiBsb29raW5nIGF0IDU5LDA5MCBnZW5lcyAtIG1hbnkgb2Ygd2hpY2ggbWF5IGJlIGJhcmVseSBleHByZXNzZWQuIFNpbmNlIHRoZXNlIGFyZSBub3JtYWxpemVkIGNvdW50cyBhY3Jvc3MgdGhlIGRhdGFzZXQsIHdlIGNhbiBmaWx0ZXIgb3V0IGxvdy1leHByZXNzaW9uIGdlbmVzIHRvIG1ha2UgYHRpc3N1ZV9kYXRhX2ZpbHRlcmVkLmRmYC4gWWVzLCB0aGlzIHdvdWxkIGFnYWluIGJlIGNvbnNpZGVyZWQgYSBmb3JtIG9mICpmZWF0dXJlIGVsaW1pbmF0aW9uKi4gSW4gZ2VuZXJhbCB3ZSB3aWxsOg0KDQoxLiAgQ29udmVydCB0aGUgcm93IG5hbWVzIHRvIHRoZWlyIG93biBjb2x1bW4uDQoyLiAgQ2FsY3VsYXRlIHRoZSBtZWFuIG9mIHRoZSBub3JtYWxpemVkIGNvdW50cyBhY3Jvc3MgZWFjaCBvYnNlcnZhdGlvbiBhbmQgZmlsdGVyIGZvciBhIG1pbmltdW0gb2YgMC41Lg0KMy4gIENhbGN1bGF0ZSB0aGUgdmFyaWFuY2UgYWNyb3NzIGVhY2ggb2JzZXJ2YXRpb24gYXMgYSBiYWNrdXAgcGxhbi4NCg0KYGBge3J9DQoNCnN5c3RlbS50aW1lKA0KIyBUcmltIHRoZSB0aXNzdWUgZGF0YSBkb3duDQp0aXNzdWVfZGF0YV9maWx0ZXJlZC5kZiA8LQ0KDQogIHRpc3N1ZV9kYXRhLmRmICU+JSANCg0KICAjIENvbnZlcnQgdGhlIHJvdyBuYW1lcyB0byBhbiBhY3R1YWwgY29sdW1uDQogIHJvd25hbWVzX3RvX2NvbHVtbih2YXI9ImdlbmUiKSAlPiUgDQoNCiAgIyBTZXQgdXAgdGhlIHRhYmxlIHRvIHBlcmZvcm0gcm93LXdpc2Ugb3BlcmF0aW9ucw0KICByb3d3aXNlKCkgJT4lIA0KDQogICMgQ2FsY3VsYXRlIHRoZSBtZWFuIGV4cHJlc3Npb24gb2YgZWFjaCBnZW5lIGFjcm9zcyBhbGwgdGlzc3VlIHNhbXBsZXMNCiAgbXV0YXRlKG1lYW4gPSAuLi4oY19hY3Jvc3Mod2hlcmUoaXMubnVtZXJpYykpKSkgJT4lIA0KDQogICMgRmlsdGVyIGZvciBzYW1wbGVzIHdpdGggbG93IGV4cHJlc3Npb24NCiAgZmlsdGVyKG1lYW4gPiAwLjUpICU+JSANCg0KICAjIENhbGN1bGF0ZSBvdmVyYWxsIHZhcmlhbmNlIGluIGNhc2Ugd2UgbmVlZCB0byBtYWtlIG91ciBkYXRhc2V0IHNtYWxsZXINCiAgbXV0YXRlKHZhcmlhbmNlID0gLi4uKGNfYWNyb3NzKHdoZXJlKGlzLm51bWVyaWMpKSkpICU+JSANCg0KICAjIEFycmFuZ2Ugc2FtcGxlcyBieSBkZXNjZW5kaW5nIHZhcmlhbmNlDQogIGFycmFuZ2UoZGVzYyh2YXJpYW5jZSkpICU+JSANCg0KICAjIFJlbW92ZSB0aGUgZ3JvdXBpbmcgc3BlY2lmaWNhdGlvbg0KICB1bmdyb3VwKCkNCg0KKQ0KDQojIFRha2UgYSBsb29rIGF0IHRoZSBmaW5hbCByZXN1bHRzDQpoZWFkKHRpc3N1ZV9kYXRhX2ZpbHRlcmVkLmRmKQ0KDQojIGhvdyBiaWcgaXMgb3VyIGZpbHRlcmVkIGRhdGEgZnJhbWU/DQpkaW0odGlzc3VlX2RhdGFfZmlsdGVyZWQuZGYpDQpgYGANCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjIyA0LjAuNCBNb3JlIGRhdGEgd3JhbmdsaW5nIHRvIHRyYW5zcG9zZSBvdXIgUk5Bc2VxIGRhdGENCg0KTm93IHRoYXQgd2UndmUgZmlsdGVyZWQgb3VyIGRhdGEgZG93biB0byBcfjI5ayBnZW5lcywgd2UnbGwgcnVuIHRocm91Z2ggdHdvIG1vcmUgc3RlcHM6DQoNCjEuICBXZSdsbCB0cmFuc3Bvc2Ugb3VyIGRhdGEgdXNpbmcgYSBjb21iaW5hdGlvbiBvZiBgcGl2b3RfbG9uZ2VyKClgIGFuZCBgcGl2b3Rfd2lkZXIoKWAuDQoyLiAgV2UnbGwgdXNlIGEgc2VyaWVzIG9mIHN0cmluZyBtYXRjaGVzIGFuZCBqb2lucyB0byBhZGQgc29tZSBzYW1wbGUgaW5mb3JtYXRpb24gdG8gb3VyIGRhdGEuIEl0IHdvbid0IGJlIHVzZWQgZm9yIG91ciBhbmFseXNpcyBidXQgd2lsbCBiZSB1c2VkIHdoZW4gd2UgcGxvdCB0aGUgcmVzdWx0cyENCjMuICBMYXN0bHkgd2UnbGwgY29udmVydCB0aGUgcmVsZXZhbnQgcGFydHMgb2Ygb3VyIGRhdGEgZnJhbWUgdG8gYSBtYXRyaXguIFRoZXJlIGlzIGEgaHVnZSBtZW1vcnkgc2F2aW5ncyBpbiB0aGUgYWxnb3JpdGhtIHdoZW4gd29ya2luZyB3aXRoIGEgbWF0cml4IGFuZCB5b3UgaGF2ZSBsaW1pdGVkIG1lbW9yeSBvbiB5b3VyIFJTdHVkaW8gSHVicyENCg0KYGBge3J9DQojIFlvdSBuZWVkIHRvIHRyYW5zcG9zZSB0aGUgZGF0YS4gDQojIFdlIGNhbiBkbyBpdCB3aXRoIGRwbHlyIHRvIGtlZXAgaXQgYXMgYSBkYXRhIGZyYW1lIGFuZCB0byBhZGQgc29tZSBpbmZvDQoNCnRpc3N1ZV9STkFzZXEuZGYgPC0NCg0KICB0aXNzdWVfZGF0YV9maWx0ZXJlZC5kZiAlPiUgDQogIA0KICAjIHRyaW0gZG93biB0aGUgY29sdW1ucyB0byBkcm9wIG1lYW4vdmFyaWFuY2UNCiAgZHBseXI6OnNlbGVjdCgxOjg5KSAlPiUgDQoNCiAgIyBwaXZvdCBsb25nZXINCiAgcGl2b3RfbG9uZ2VyKGNvbHM9YygyOjg5KSwgbmFtZXNfdG8gPSAuLi4sIHZhbHVlc190byA9IC4uLikgJT4lIA0KDQogICMgcmVkaXN0cmlidXRlIHRoZSBnZW5lIG5hbWVzIHRvIGNvbHVtbnMNCiAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9IGdlbmUsIHZhbHVlc19mcm9tID0gLi4uKQ0KYGBgDQoNCmBgYHtyfQ0KIyBXZSB3YW50IHRvIGFkZCBzb21lIGFkZGl0aW9uYWwgc2FtcGxlIGluZm9ybWF0aW9uIGJlZm9yZSBhc3Nlc3NpbmcgdGhlIGRhdGENCg0KdGlzc3VlX1JOQXNlcS5kZiA8LQ0KDQogIHRpc3N1ZV9STkFzZXEuZGYgJT4lIA0KDQogICMgR3JhYiBqdXN0IHRoZSBzYW1wbGUgbmFtZXMNCiAgZHBseXI6OnNlbGVjdChzYW1wbGUpICU+JSANCg0KICAjIEdyYWIgaW5mb3JtYXRpb24gZnJvbSBpdCBsaWtlIGNhc2UgbnVtYmVyLCB0aXNzdWUsIGFuZCB0aXNzdWUgbnVtYmVyDQogICMgdGFrZXMgdGhlIGZvcm0gb2YgImNhc2VYLnRpc3N1ZVkiIG9yICJjYXNlWC50aXNzdWUuTllDIiBvciAiTmVnQ29udHJvbFgiDQogICMgUmVtZW1iZXIgdGhhdCB0aGlzIHJldHVybnMgYSBMSVNUIG9mIGNoYXJhY3RlciBtYXRyaWNlcw0KICBzdHJfbWF0Y2hfYWxsKC4sIHBhdHRlcm49ciIoY2FzZShbXHddKylcLihbYS16XSspKFtcZHxcLk5ZQ10qKXwoTmVnQ29udHJvbFxkKSkiKSAlPiUgDQoNCiAgIyBCaW5kIGFsbCB0aGUgbWF0cmljZXMgZnJvbSBhbGwgdGhlIGxpc3QgZWxlbWVudHMgdG9nZXRoZXIgaW4gYSBzaW5nbGUgb2JqZWN0IChsaWtlbHkgYSBtYXRyaXgpDQogIGRvLmNhbGwocmJpbmQsIC4pICU+JSANCg0KICAjIENvbnZlcnQgdGhlIHJlc3VsdHMgdG8gYSBkYXRhIGZyYW1lDQogIGFzLmRhdGEuZnJhbWUoKSAlPiUgDQoNCiAgIyBSZW5hbWUgdGhlIGNvbHVtbnMgYmFzZWQgb24gdGhlIGNhcHR1cmUgZ3JvdXBzDQogIGRwbHlyOjpyZW5hbWUoLiwgc2FtcGxlID0gVjEsIGNhc2VfbnVtID0gVjIsIHRpc3N1ZSA9IFYzLCB0aXNzdWVfbnVtID0gVjQsIG5lZ19udW0gPSBWNSkgJT4lDQoNCiAgIyBDb2FsZXNjZSBzb21lIG9mIHRoZSBpbmZvIGR1ZSB0byBuZWdhdGl2ZSBjb250cm9sIHNhbXBsZXMgYW5kIGNsZWFuIHVwIGEgY29sdW1uDQogIG11dGF0ZShjYXNlX251bSA9IGNvYWxlc2NlKGNhc2VfbnVtLCBuZWdfbnVtKSwNCiAgICAgICAgIHRpc3N1ZV9udW0gPSBzdHJfcmVwbGFjZV9hbGwoLiR0aXNzdWVfbnVtLCBwYXR0ZXJuID0gIlxcLiIsIHJlcGxhY2UgPSAiIikpICU+JQ0KICANCiAgIyBEcm9wIHRoZSBuZWdfbnVtIGNvbHVtbg0KICBkcGx5cjo6c2VsZWN0KDE6NCkgJT4lDQogIA0KICAjIEpvaW4gdGhpcyByZXN1bHQgdG8gdGhlIFJOQXNlcSBpbmZvDQogIGZ1bGxfam9pbiguLCB5PXRpc3N1ZV9STkFzZXEuZGYsIGJ5PWMoInNhbXBsZSIgPSAic2FtcGxlIikpICU+JQ0KICANCiAgIyBKb2luIHRoYXQgcmVzdWx0IHRvIGdyYWIgdmlyYWwgbG9hZCBpbmZvcm1hdGlvbg0KICByaWdodF9qb2luKHBhdGllbnRfdmlyYWxfbG9hZC5kZiwgeT0uLCBieT1jKCJjYXNlX251bSIgPSAiY2FzZV9udW0iKSkNCg0KIyBMb29rIGF0IHRoZSByZXN1bHRpbmcgZGF0YWZyYW1lDQpoZWFkKHRpc3N1ZV9STkFzZXEuZGYpDQpgYGANCg0KYGBge3J9DQojIEhvdyBtYW55IHRpc3N1ZSB0eXBlcyBkbyB3ZSBoYXZlPw0KdGFibGUodGlzc3VlX1JOQXNlcS5kZiR0aXNzdWUpDQpgYGANCg0KYGBge3J9DQojIEdlbmVyYXRlIGEgbWF0cml4IHZlcnNpb24gb2Ygb3VyIGRhdGEgYnV0IGRyb3AgdGhlIHNhbXBsZSBtZXRhZGF0YSENCnRpc3N1ZV9STkFzZXEubXggPC0gYXMubWF0cml4KHRpc3N1ZV9STkFzZXEuZGZbLC4uLl0pDQoNCiMgaGVhZCh0aXNzdWVfUk5Bc2VxLm14KQ0Kc3RyKHRpc3N1ZV9STkFzZXEubXgpDQpkaW0odGlzc3VlX1JOQXNlcS5teCkNCmBgYA0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMgNC4xLjAgdC1EaXN0cmlidXRlZCBTdG9jaGFzdGljIE5laWdoYm91ciBFbWJlZGRpbmcgd2l0aCB0aGUgYFJ0c25lYCBwYWNrYWdlDQoNCldlIG5vdyBoYXZlIGEgc29tZXdoYXQgbW9yZSBjb21wbGV4IGRhdGFzZXQuIFdlIGFyZSBzdGlsbCBzaG9ydCBvbiBhY3R1YWwgc2FtcGxlcyAobm93IG9ic2VydmF0aW9ucykgYnV0IDg4IG9ic2VydmF0aW9ucyBhbmQgbmVhcmx5IDMwSyBmZWF0dXJlcyBpc24ndCBzbyBiYWQuIEEgYnJvYWQgcXVlc3Rpb24gd2UgbWF5IHdpc2ggdG8gYXNrIHdpdGggc3VjaCBhIGRhdGEgc2V0IGlzIGlmIHRoZXJlIGlzIGFuIHVuZGVybHlpbmcgc3RydWN0dXJlIHRvIHRoZXNlIHNhbXBsZXMgLSBpLmUuIGRvIHdlIHNlZSBncm91cGluZyBiYXNlZCBvbiB0aXNzdWUgdHlwZSwgb3IgcGVyaGFwcyBldmVuIHNhbXBsZSBwcmVwYXJhdGlvbi4NCg0KKip0LURpc3RyaWJ1dGVkIFN0b2NoYXN0aWMgTmVpZ2hib3VyIEVtYmVkZGluZyoqIG9yICoqdC1TTkUqKiBpcyBhIHdheSBmb3IgdXMgdG8gcHJvamVjdCBvdXIgaGlnaC1kaW1lbnNpb24gZGF0YSBvbnRvIGEgbG93ZXIgZGltZW5zaW9uIHdpdGggdGhlIGFpbSBhdCAqcHJlc2VydmluZyB0aGUgbG9jYWwgc2ltaWxhcml0aWVzIHJhdGhlciB0aGFuIGdsb2JhbCBkaXNwYXJpdHkqLiBXaGVuIGxvb2tpbmcgYXQgZGF0YSBwb2ludHMsIHQtU05FIHdpbGwgYXR0ZW1wdCB0byBwcmVzZXJ2ZSB0aGUgbG9jYWwgbmVpZ2hib3VyaW5nIHN0cnVjdHVyZS4gQXMgdGhlIGFsZ29yaXRobSBwb3VycyBvdmVyIHRoZSBkYXRhLCBpdCBjYW4gdXNlIGRpZmZlcmVudCB0cmFuc2Zvcm1hdGlvbnMgZm9yIGRpZmZlcmVudCByZWdpb25zIGFzIGl0IGF0dGVtcHRzIHRvIHRyYW5zZm9ybSBldmVyeXRoaW5nIHRvIGEgbG93ZXIgZGltZW5zaW9uLiBJdCBpcyBjb25zaWRlcmVkICJpbmNyZWRpYmx5IGZsZXhpYmxlIiBhdCBmaW5kaW5nIGxvY2FsIHN0cnVjdHVyZSB3aGVyZSBvdGhlciBhbGdvcml0aG1zIG1heSBub3QuDQoNClRoaXMgZmxleGliaWxpdHkgaXMgYWNjb21wbGlzaGVkIHRocm91Z2ggMiBzdGVwczoNCg0KMC4gICpSZWR1Y2UgZGltZW5zaW9uYWxpdHkgb2YgeW91ciBkYXRhIGZlYXR1cmVzIHdpdGggUENBISoNCjEuICBHZW5lcmF0ZSBhIHByb2JhYmlsaXR5IGRpc3RyaWJ1dGlvbiBiZXR3ZWVuIGFsbCBwYWlycyBieSBtYWtpbmcgc2ltaWxhciBvYmplY3RzIGhpZ2hseSBwcm9iYWJseSBhbmQgYXNzaWduaW5nIGRpc3NpbWlsYXIgb2JqZWN0cyBhIGxvdyBwcm9iYWJpbGl0eS4NCjIuICBEZWZpbmUgYSBzaW1pbGFyIHByb2JhYmlsaXR5IGRpc3RyaWJ1dGlvbiBmb3IgdGhlIHNhbXBsZXMgaW4gYSBsb3dlciBkaW1lbnNpb24gd2hpbGUgbWluaW1pemluZyB0aGUgZGl2ZXJnZW5jZSBiZXR3ZWVuIHRoZSB0d28gZGlzdHJpYnV0aW9ucyBiYXNlZCBvbiBhIGRpc3RhbmNlIG1ldHJpYyBiZXR3ZWVuIHBvaW50cyBpbiB0aGUgbG93ZXIgZGltZW5zaW9uLg0KDQpXZSdsbCBkaXNjdXNzIG1vcmUgYWJvdXQgaG93IHRoaXMgYWxnb3JpdGhtIGFmZmVjdHMgaW50ZXJwcmV0YXRpb24gYWZ0ZXIgc2VlaW5nIHRoZSByZXN1bHRzLCBidXQgdGhpcyBpcyBjb25zaWRlcmVkIGFuICoqKmV4cGxvcmF0b3J5KioqIGRhdGEgdmlzdWFsaXphdGlvbiB0b29sLCByYXRoZXIgdGhhbiAqKipleHBsYW5hdG9yeSoqKi4NCg0KVG8gcHJvZHVjZSBvdXIgdC1TTkUgcHJvamVjdGlvbiB3ZSdsbCB1c2UgdGhlIGBSdHNuZSgpYCBmdW5jdGlvbiBmcm9tIHRoZSBwYWNrYWdlIG9mIHRoZSBzYW1lIG5hbWUuIFNvbWUgaW1wb3J0YW50IHBhcmFtZXRlcnMgYXJlOg0KDQotICAgYFhgIGlzIG91ciBkYXRhIG1hdHJpeCB3aGVyZSBlYWNoIHJvdyBpcyBhbiBvYnNlcnZhdGlvbg0KDQotICAgYGRpbXNgIHNldHMgdGhlIG51bWJlciBvZiBkaW1lbnNpb25zIHdlJ2QgbGlrZSB0byBwcm9qZWN0IG9udG8gKGRlZmF1bHQgaXMgMikuDQoNCi0gICBgaW5pdGlhbF9kaW1zYCBzZXRzIHRoZSBudW1iZXIgb2YgZGltZW5zaW9ucyB0aGF0IHNob3VsZCBiZSByZXRhaW5lZCBpbiB0aGUgaW5pdGlhbCBQQ0Egc3RlcCAoRGVmYXVsdCA1MCkuDQoNCi0gICBgcGVycGxleGl0eWAgYSBudW1lcmljIHBhcmFtZXRlciB0aGF0IHR1bmVzIGJldHdlZW4gbG9jYWwgYW5kIGdsb2JhbCBhc3BlY3RzIG9mIHlvdXIgZGF0YS4NCg0KICAgIC0gICBUaGlzIHBhcmFtZXRlciBpcyBhIGd1ZXNzIGFzIHRvIGhvdyBtYW55IGNsb3NlIG5laWdoYm91cnMgYSBwb2ludCBtYXkgaGF2ZS4gSWYgeW91IGhhdmUgYSBzZW5zZSBvZiBzYW1wbGUgdHlwZXMgKG9yIGNsdXN0ZXJzISkgYWhlYWQgb2YgdGltZSwgeW91IGNvdWxkIHRyeSB0byBwbGF5IHdpdGggdGhpcyB2YWx1ZSAoZGVmYXVsdCBpcyAzMCkuDQoNCiAgICAtICAgQWNjb3JkaW5nIHRvIHRoZSBhbGdvcml0aG0gdGhpcyB2YWx1ZSBzaG91bGQgZm9sbG93IHRoaXMgcnVsZTogJHBlcnBsZXhpdHkgKiAzIFxsdCBucm93KFgpIC0xJA0KDQotICAgYHBjYV9zY2FsZWAgaXMgYSBib29sZWFuIHRvIHNldCBpZiB0aGUgaW5pdGlhbCBQQ0Egc3RlcCBzaG91bGQgdXNlIHNjYWxlZCBkYXRhLg0KDQotICAgYG1heF9pdGVyYCBpcyB0aGUgbWF4aW11bSBudW1iZXIgb2YgaXRlcmF0aW9ucyBpbiB0aGUgYWxnb3JpdGhtIChkZWZhdWx0IGlzIDEwMDApLg0KDQpgYGB7cn0NCiMgUnRzbmUgcHJlZmVycyB1c2luZyBhIG1hdHJpeCBmb3IgbWVtb3J5IGlzc3Vlcw0KIyBzZXQgYSBzZWVkIGZvciByZXByb2R1Y2libGUgcmVzdWx0cw0Kc2V0LnNlZWQoMjAyNCkNCg0KIyBUcnkgZm9yIHBlcnBsZXhpdHkgb2YgMzAgY2FuIGdvIGFzIGhpZ2ggYXMgMjkgYmVmb3JlIGNyYXNoDQojIFdlIGhhdmUganVzdCA5MCBzYW1wbGVzLCBidXQgYmV0d2VlbiAxLTUyIHNhbXBsZXMgcGVyICJncm91cCINCnRpc3N1ZV90c25lIDwtIFJ0c25lKC4uLiwgDQogICAgICAgICAgICAgICAgICAgICBkaW1zPTIsIA0KICAgICAgICAgICAgICAgICAgICAgcGVycGxleGl0eT01LCANCiAgICAgICAgICAgICAgICAgICAgIHZlcmJvc2U9VFJVRSwgDQogICAgICAgICAgICAgICAgICAgICBwY2Ffc2NhbGUgPSBUUlVFLA0KICAgICAgICAgICAgICAgICAgICAgbWF4X2l0ZXIgPSAxNTAwKSANCmBgYA0KDQpgYGB7cn0NCiMgV2hhdCBkb2VzIG91ciB0LVNORSBvYmplY3QgbG9vayBsaWtlPw0Kc3RyKC4uLikNCmBgYA0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMjIDQuMS4xIEV4dHJhY3QgaW5mb3JtYXRpb24gZnJvbSBvdXIgdHNuZSBvYmplY3QNCg0KTG9va2luZyBhYm92ZSBhdCB0aGUgcmVzdWx0IG9mIG91ciB0LVNORSBhbmFseXNpcywgd2UgY2FuIG5vdGljZSBhIGZldyB0aGluZ3MuLi4NCg0KMS4gIFdlIGdldCBiYWNrIHRoZSBudW1iZXIgb2Ygb2JqZWN0cyB3ZSBwdXQgaW46IDg4Lg0KMi4gIFRoZSBlbGVtZW50IGBZYCBpcyBhbiA4OHgyIG1hdHJpeCBob2xkaW5nIHRoZSBjb29yZGluYXRlcyBmb3Igb3VyIG5ldyBwcm9qZWN0aW9uLg0KMy4gIFRoZXJlIGFyZSBubyBlaWdlbnZlY3RvcnMgb3Igb3RoZXIgaW5mb3JtYXRpb24gYWJvdXQgdGhlIHZhcmlhYmxlcyBvciBkaW1lbnNpb25zIG9yIGhvdyB0aGV5IHdlcmUgdXNlZCBpbiB0aGUgcHJvamVjdGlvbiENCg0KVGhhdCdzIHJpZ2h0LCB0aGVyZSBpcyBubyB1bmRlcmx5aW5nIGluZm9ybWF0aW9uIGZvciBtYXBwaW5nIGJhY2sgdG8gb3VyIG9yaWdpbmFsIGRhdGFzZXQuIEl0J3MgYSBjb21wbGV0ZWx5IGJsYWNrIGJveCB3aXRoIG5vIHdheSB0byByZXZlcnNlIGVuZ2luZWVyIHRoZSBwcm9jZXNzLiBUaGF0J3MgYmVjYXVzZSB0aGUgcHJvY2VzcyBpdHNlbGYgaXMgKioqc3RvY2hhc3RpYyoqKiEgV2hlcmVhcyBQQ0Egd2FzIGEgZGV0ZXJtaW5pc3RpYyBwcm9jZXNzIC0gcmVwZWF0YWJsZSB0aGUgc2FtZSB3YXkgZXZlcnkgdGltZSB3aXRoIHRoZSBzYW1lIGRhdGEgLSB0aGF0IGlzIG5vdCB0aGUgY2FzZSBmb3IgdC1TTkUuIFRoYXQncyB3aHkgZXZlbiB0aG91Z2ggaXQgY2FuIGJlIHF1aXRlIHBvd2VyZnVsIGluIGlkZW50aWZ5aW5nIGxvY2FsIHNpbWlsYXJpdGllcywgKioqdC1TTkUgZG9lcyBub3QgcHJvdmlkZSBhIG1hdGhlbWF0aWNhbCBwYXRod2F5IGJhY2sgdG8gb3VyIG9yaWdpbmFsIGRhdGEhKioqDQoNCkxldCdzIGV4dHJhY3QgdGhlIGluZm9ybWF0aW9uLCBjb21iaW5lIGl0IHdpdGggb3VyIHNhbXBsZSBpbmZvcm1hdGlvbiBhbmQgcHJvamVjdCBpdCB1c2luZyBgZ2dwbG90MmAuIFdlIGNhbiBkbyB0aGlzIHdpdGggYSBzY2F0dGVycGxvdCBzaW5jZSB3ZSBoYXZlIGEgc2V0IG9mIHgveSBjb29yZGluYXRlcyB3ZSBjYW4gd29yayB3aXRoLg0KDQpgYGB7cn0NCiMgQnVpbGQgb3VyIG5ldyBkYXRhIGZyYW1lIGZyb20gdGhlIFkgdmFsdWVzDQp0aXNzdWVfdHNuZS5kZiA8LSBkYXRhLmZyYW1lKHguY29vcmQgPSAuLi4sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHkuY29vcmQgPSAuLi4pDQoNCiMgQWRkIGluIG91ciBzYW1wbGUgaW5mb3JtYXRpb24NCnRpc3N1ZV90c25lLmRmIDwtIGNiaW5kKHRpc3N1ZV9STkFzZXEuZGZbLDE6Nl0sIHRpc3N1ZV90c25lLmRmKQ0KDQojIEZpeCB1cCB0aGUgaW5mb3JtYXRpb24ganVzdCBhIGxpdHRsZSBiaXQgdG8gcmVtb3ZlIE5BIHZpcmFsIGxvYWQgaW5mb3JtYXRpb24NCnRpc3N1ZV90c25lLmRmIDwtDQogIHRpc3N1ZV90c25lLmRmICU+JSANCiAgIyByZXBsYWNlIE5BcyB3aXRoIEROVyAoZGlkIG5vdCB3b3JrKQ0KICBtdXRhdGUodmlyYWxfbG9hZCA9IHJlcGxhY2VfbmEodmlyYWxfbG9hZCwgcmVwbGFjZSA9ICJETlciKSkNCg0KaGVhZCh0aXNzdWVfdHNuZS5kZikNCmBgYA0KDQpgYGB7ciwgZmlnLndpZHRoPTIwLCBmaWcuaGVpZ2h0PTIwfQ0KDQpjb21iby5jb2xvdXJzID0gYyhicmV3ZXIucGFsKDEyLCAiUGFpcmVkIiksIGJyZXdlci5wYWwoMTIsICJTZXQzIiksIGJyZXdlci5wYWwoOCwgIlNldDEiKSkNCg0KIyAxLiBEYXRhDQpnZ3Bsb3QoZGF0YSA9IHRpc3N1ZV90c25lLmRmKSArDQogICMgMi4gQWVzdGhldGljcw0KICBhZXMoeCA9IHguY29vcmQsIA0KICAgICAgeSA9IHkuY29vcmQsIA0KICAgICAgY29sb3VyID0gLi4uKSArDQoNCiAgIyBUaGVtZXMNCiAgdGhlbWVfYncoKSArDQogIHRoZW1lKHRleHQgPSBlbGVtZW50X3RleHQoc2l6ZT0yMCkpICsNCg0KICAjIDMuIFNjYWxpbmcNCiAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXMgPSBjb21iby5jb2xvdXJzKSArDQoNCiAgIyA0LiBHZW9tcw0KICBnZW9tX3RleHQoYWVzKGxhYmVsID0gY2FzZV9udW0pLCBzaXplID0gMTApDQpgYGANCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjIyA0LjEuMiBJbnRlcnByZXRpbmcgb3VyIHQtU05FIHBsb3QNCg0KV2hpbGUgd2UgZG9uJ3QgaGF2ZSBhIGxvdCBvZiBzYW1wbGVzLCB5b3UgY2FuIHN0aWxsIHNlZSB0aGF0IHdlIHdlcmUgYWJsZSB0byBjbHVzdGVyICpzb21lKiBvZiBvdXIgZGF0YSBieSBjZWxsIHR5cGVzIHdpdGhvdXQgcHJvdmlkaW5nIHRoYXQgY2xhc3NpZmljYXRpb24gdG8gdGhlIGFsZ29yaXRobSEgR3JlYXQgam9iIHRlYW0hDQoNCldlIGNhbiBzZWUgdGhhdCB3ZSBnZXQgY2xvc2UgY2x1c3RlcmluZyBvZiB0aXNzdWVzIGxpa2UgcGxhY2VudGEsIGhlYXJ0LCBhbmQgYm93ZWwuIE91ciBsaXZlciBzYW1wbGVzIGFyZSBraW5kIG9mIGV2ZXJ5d2hlcmUgYnV0IHBlcmhhcHMgdXNpbmcgYSBkaWZmZXJlbnQgcGVycGxleGl0eSB3b3VsZCBwcm92aWRlIGRpZmZlcmVudCByZXN1bHRzLg0KDQpPbmUgaW50ZXJlc3RpbmcgdGhpbmcgd2UgY2FuIHNlZSBpcyB0aGF0IHJlZ2FyZGxlc3Mgb2YgdGlzc3VlIHR5cGUsIHdlIHNlZSBzb21lIHNhbXBsZXMgYXJlIGNsdXN0ZXJpbmcgdG9nZXRoZXIgYmFzZWQgb24gY2FzZSBudW1iZXIgLSBuYW1lbHkgY2FzZSBudW1iZXJzIDEsIDQgYW5kIDUgc2VlbSB0byBoYXZlIHNvbWUgc3Ryb25nIHVuZGVybHlpbmcgZXhwcmVzc2lvbiBwcm9maWxlcyB0aGF0IGNvbm5lY3QgdGhlbSBhY3Jvc3MgdGlzc3VlIHNhbXBsZXMuIFdlIG1heSBhbHNvIGJlIHNlZWluZyBmYWxzZSByZWxhdGlvbnNoaXBzIHNvIGJld2FyZSENCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjIDQuMi4wIFVuaWZvcm0gTWFuaWZvbGQgQXBwcm94aW1hdGlvbiBhbmQgUHJvamVjdGlvbg0KDQpUaGlzIGFsZ29yaXRobSBmb3IgcHJvamVjdGlvbiBpcyBpbiB0aGUgc2FtZSBmbGF2b3VyIGFzIHQtU05FIHByb2plY3Rpb24gYnV0IGhhcyBzb21lIGRpZmZlcmVuY2VzIGluY2x1ZGluZzoNCg0KMS4gIEluY3JlYXNlZCBzcGVlZCBhbmQgYmV0dGVyIHByZXNlcnZhdGlvbiBvZiB0aGUgZ2xvYmFsIHN0cnVjdHVyZSBpbiB5b3VyIGRhdGENCjIuICBBIGRpZmZlcmVudCB0aGVvcmV0aWNhbCBmb3VuZGF0aW9uIHVzZWQgdG8gYmFsYW5jZSBiZXR3ZWVuIGxvY2FsIGFuZCBnbG9iYWwgc3RydWN0dXJlDQoNCldoYXQgZG9lcyB0aGF0IG1lYW4gZm9yIHVzPyBGYXN0ZXIgcmVzdWx0cywgYW5kIG1vcmUgaW50ZXJwcmV0aXZlIHJlc3VsdHMhIE90aGVyd2lzZSB0aGUgc2FtZSBpc3N1ZXMgY2FuIGFwcGx5LiBUaGUgc2V0dXAgaXMgc2xpZ2h0bHkgZWFzaWVyIHdpdGggZmV3IG9wdGlvbnMgdG8gY2hhbmdlIGlmIHlvdSBsZWF2ZSB0aGUgZGVmYXVsdHMuIFdlIGNhbiBhY2Nlc3MgYHVtYXAoKWAgZnJvbSB0aGUgcGFja2FnZSBvZiB0aGUgc2FtZSBuYW1lLiBZb3UgbWF5IGFsc28gYWx0ZXIgdGhlIGRlZmF1bHQgbWV0aG9kcyBieSBjcmVhdGluZyBhIGB1bWFwLmRlZmF1bHRzYCBvYmplY3QuIE1vcmUgaW5mb3JtYXRpb24gb24gdGhhdCBbaGVyZV0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL3VtYXAvdW1hcC5wZGYpDQoNCkZvciBtb3JlIHRpbmtlcmluZywgeW91IGNhbiBjaG9vc2UgdG8gdXNlIHRoZSBgdXdvdGAgW3BhY2thZ2VdKGh0dHBzOi8vcmRyci5pby9jcmFuL3V3b3QvKSBpbnN0ZWFkIHdoZXJlIHRoZSBgdW1hcCgpYCBmdW5jdGlvbiBoYXMgbW9yZSBvcHRpb25zIHRoYXQgYXJlIGVhc2lseSBtb2RpZmllZC4NCg0KYGBge3J9DQojIFNldCBvdXIgc2VlZA0Kc2V0LnNlZWQoMjAyNCkNCg0KIyBHZW5lcmF0ZSBvdXIgcHJvamVjdGlvbg0KdGlzc3VlX3VtYXAgPC0gLi4uKHRpc3N1ZV9STkFzZXEubXgpDQpgYGANCg0KYGBge3J9DQojIFdoYXQgZG9lcyB0aGUgVU1BUCBvYmplY3QgbG9vayBsaWtlPw0Kc3RyKHRpc3N1ZV91bWFwKQ0KYGBgDQoNCiMjIyA0LjIuMSBFeHRyYWN0IGluZm9ybWF0aW9uIGZyb20gb3VyIFVNQVAgb2JqZWN0DQoNCkxvb2tpbmcgYXQgb3VyIFVNQVAgb2JqZWN0IGB0aXNzdWVfdW1hcGAgd2Ugc2VlIGl0IGhvdXNlcyB0aGUgcHJvamVjdGlvbiBwYXJhbWV0ZXJzIHVzZWQgYnV0IGFsc28gc29tZSBhZGRpdGlvbmFsIHZhcmlhYmxlczoNCg0KMS4gIGBkYXRhYDogaG9sZHMgb3VyIG9yaWdpbmFsIGRhdGEgbWF0cml4Lg0KMi4gIGBsYXlvdXRgOiBjb250YWlucyB0aGUgcHJvamVjdGlvbiBjb29yZGluYXRlcyB3ZSBuZWVkIGZvciBwbG90dGluZyB0aGUgZGF0YS4NCjMuICBga25uYDogYSB3ZWlnaHRlZCBrLW5lYXJlc3QgbmVpZ2hib3VyIGdyYXBoLiBUaGlzIGlzIGEgZ3JhcGggdGhhdCBjb25uZWN0cyBlYWNoIG9ic2VydmF0aW9uIHRvIGl0cyBuZWFyZXN0IGsgbmVpZ2hib3Vycy4gVGhpcyBnZW5lcmF0ZXMgdGhlIGZpcnN0IHRvcG9sb2dpY2FsIHJlcHJlc2VudGF0aW9uIG9mIHRoZSBkYXRhIC0gbGlrZSBhbiBpbml0aWFsIHNrZXRjaC4NCg0KWW91IG1heSBub3RpY2UgYWdhaW4gdGhhdCB0aGVyZSBpcyBubyBkYXRhIHRoYXQgc3VnZ2VzdHMgaG93IHdlICoqKmFycml2ZWQqKiogYXQgdGhpcyBzb2x1dGlvbi4gVGhlcmUgYXJlICpubyBlaWdlbnZlY3RvcnMgb3IgdmFsdWVzKiB0byByZXZlcnNlIHRoZSBwcm9qZWN0aW9uIQ0KDQpMZXQncyBleHRyYWN0IHRoZSBgbGF5b3V0YCBpbmZvcm1hdGlvbiwgY29tYmluZSBpdCB3aXRoIG91ciBzYW1wbGUgaW5mb3JtYXRpb24gYW5kIHByb2plY3QgaXQgdXNpbmcgYGdncGxvdDJgDQoNCmBgYHtyfQ0KIyBSZS1tYXAgb3VyIHByb2plY3Rpb24gcG9pbnRzIHdpdGggb3VyIHRpc3N1ZSBkYXRhDQoNCnRpc3N1ZV91bWFwLmRmIDwtIGRhdGEuZnJhbWUoeC5jb29yZCA9IHRpc3N1ZV91bWFwJC4uLiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeS5jb29yZCA9IHRpc3N1ZV91bWFwJC4uLikNCg0KdGlzc3VlX3VtYXAuZGYgPC0gY2JpbmQodGlzc3VlX1JOQXNlcS5kZlssMTo2XSwgdGlzc3VlX3VtYXAuZGYpDQoNCnRpc3N1ZV91bWFwLmRmIDwtDQogIHRpc3N1ZV91bWFwLmRmICU+JSANCiAgIyByZXBsYWNlIE5BcyB3aXRoIEROVyAoZGlkIG5vdCB3b3JrKQ0KICBtdXRhdGUodmlyYWxfbG9hZCA9IHJlcGxhY2VfbmEodmlyYWxfbG9hZCwgcmVwbGFjZSA9ICJETlciKSkNCg0KaGVhZCh0aXNzdWVfdW1hcC5kZikNCmBgYA0KDQpgYGB7ciwgZmlnLndpZHRoPTIwLCBmaWcuaGVpZ2h0PTIwfQ0KDQpjb21iby5jb2xvdXJzID0gYyhicmV3ZXIucGFsKDEyLCAiUGFpcmVkIiksIGJyZXdlci5wYWwoMTIsICJTZXQzIiksIGJyZXdlci5wYWwoOCwgIlNldDEiKSkNCg0KIyAxLiBEYXRhDQpnZ3Bsb3QoZGF0YSA9IC4uLikgKw0KICAjIDIuIEFlc3RoZXRpY3MNCiAgYWVzKHggPSB4LmNvb3JkLCANCiAgICAgIHkgPSB5LmNvb3JkLCANCiAgICAgIGNvbG91ciA9IHRpc3N1ZSkgKw0KDQogICMgVGhlbWVzDQogIHRoZW1lX2J3KCkgKw0KICB0aGVtZSh0ZXh0ID0gZWxlbWVudF90ZXh0KHNpemU9MjApKSArDQoNCiAgIyAzLiBTY2FsaW5nDQogIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzID0gY29tYm8uY29sb3VycykgKw0KDQogICMgNC4gR2VvbXMNCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IGNhc2VfbnVtKSwgc2l6ZSA9IDEwKQ0KYGBgDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyMgNC4yLjIgSW50ZXJwcmV0aW5nIG91ciBVTUFQIHJlc3VsdA0KDQpTbyBpdCBsb29rcyBsaWtlIHdpdGhvdXQgbXVjaCB0aW5rZXJpbmcgd2UgcmV0cmlldmVkIGEgZmFpcmx5IG5pY2UgcmVzdWx0LiBXZSBjYW4gc2VlIG5vdyB0aGF0IGxpdmVyIGFuZCBraWRuZXkgYXJlIG1vcmUgY2xvc2VseSBncm91cGVkIGFzIHRpc3N1ZXMsIHdoaWxlIGhlYXJ0IHNhbXBsZXMgZ2VuZXJhbGx5IGNsdXN0ZXIgdG9nZXRoZXIgc3RpbGwuIEJvd2VsIGFuZCBqZWp1bnVtIGFwcGVhciBzcGF0aWFsbHkgZ3JvdXBlZCBhbmQgb3VyIHBsYWNlbnRhIHNhbXBsZXMgYXJlIHN0aWxsIGNsb3NlIHRvIGVhY2ggb3RoZXIuIFRoZSBjbHVzdGVyaW5nIHdlIHNhdyB3aXRoIHNhbXBsZXMgMSwgNCwgYW5kIDUgYXBwZWFyIHRvIGJlIGxlc3Mgc2V2ZXJlLg0KDQpUaGVyZSBkb2VzIGFwcGVhciB0byBiZSBzb21lIHN0cnVjdHVyZSBiZXR3ZWVuIHRoZSBsdW5nIHNhbXBsZXMgaW4gZGlmZmVyZW50IGNhc2UgbnVtYmVycyBzbyB0aGlzIG1pZ2h0IGJlIGFuIGF2ZW51ZSB0byBleHBsb3JlIG5leHQgdG8gdHJ5IGFuZCBzZWUgaWYgdGhlcmUgdHJ1bHkgaXMgYSByZWxhdGlvbnNoaXAgYmV0d2VlbiB0aGVzZSBncm91cHMuDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIDUuMC4wIENsYXNzIHN1bW1hcnkNCg0KOjo6IHthbGlnbj0iY2VudGVyIn0NCjxpbWcgc3JjPSJodHRwczovL2dpdGh1Yi5jb20vY2Ftb2svQ1NCX0NvdXJzZV9NYXRlcmlhbHMvYmxvYi9tYWluL0FkdlZpei9Jc3RoaXNfY2x1c3Rlci5wbmc/cmF3PXRydWUiIHdpZHRoPSI3MDAiLz4NCg0KV2hpbGUgdC1TTkUgYW5kIFVNQVAgcHJvZHVjZSBwcm9qZWN0aW9ucyB0byBwcm9kdWNlIGNsdXN0ZXJlZCBkYXRhLCB5b3UgaGF2ZSBubyByb3V0ZSBiYWNrIHRvIHVuZGVyc3RhbmRpbmcgdGhlaXIgcmVsYXRpb25zaGlwcy4gUENBLCBvbiB0aGUgb3RoZXIgaGFuZCwgaXMgc3RyaWN0bHkgYSBkaW1lbnNpb24gcmVkdWN0aW9uIHRvb2wuIEl0IGRvZXMgbm90IHBsYWNlIG9yIGFzc2lnbiBkYXRhcG9pbnRzIHRvIGFueSBncm91cHMgQlVUIGl0IGlzIHVzZWZ1bCB0byB1c2Ugb24gbGFyZ2UgZGF0YXNldHMgKnByaW9yKiB0byBjbHVzdGVyaW5nIQ0KOjo6DQoNClRvZGF5IHdlIHRvb2sgYSBkZWVwIGRpdmUgaW50byBwcmluY2lwYWwgY29tcG9uZW50IGFuYWx5c2lzLiBUaGVyZSBhcmUgb2YgY291cnNlIGRpZmZlcmVudCB2YXJpYW50cyBvZiB0aGlzIGJhc2VkIG9uIHRoZSBhc3N1bXB0aW9ucyB5b3UgY2FuIG1ha2UgYWJvdXQgeW91ciBvYnNlcnZhdGlvbnMgYW5kIHZhcmlhYmxlcyBsaWtlIGluZGVwZW5kZW50IGNvbXBvbmVudCBhbmFseXNpcyAoSUNBLCBub24tR2F1c3NpYW4gZmVhdHVyZXMpIGFuZCBtdWx0aXBsZSBjb3JyZXNwb25kZW5jZSBhbmFseXNpcyAoTUNBLCBjYXRlZ29yaWNhbCBmZWF0dXJlcykuIFNvbWUgYWRkaXRpb25hbCBtZXRob2RzIGNhbiBhbHNvIGJlIHVzZWQgdG8gc3RvcmUgdGhlIHRyYW5zZm9ybWF0aW9uIGxpa2UgYSBQQ0EgZG9lcywgbm90YWJseSB2YXJpYXRpb25hbCBhdXRvZW5jb2RlcnMgKFZBRSkuDQoNCk92ZXJhbGwgd2Ugc2hvdWxkIHJlbWVtYmVyIHRoYXQgd2hpbGUgUENBIGNhbiBoYXZlIHByb2JsZW1zIGluIGdlbmVyYXRpbmcgaXQncyBmZWF0dXJlIGV4dHJhY3Rpb24sIGl0IGlzICoqKmRldGVybWluaXN0aWMgYW5kIHJlcGVhdGFibGUqKiouIEFsc28sIHRoZSBmaW5hbCByZXN1bHRzIGFyZSBwcm92aWRlZCBpbiBzdWNoIGEgd2F5IHRoYXQgKm5ldyogb2JzZXJ2YXRpb25zIGNvdWxkIGJlIHRyYW5zZm9ybWVkIGFuZCBwcm9qZWN0ZWQgb250byB0aGUgc2FtZSBwcmluY2lwYWwgY29tcG9uZW50cy4gWW91IGNhbiBhbHNvIGZlZWQgdGhlc2UgY29tcG9uZW50cyBiYWNrIGludG8gY2x1c3RlcmluZyBhbGdvcml0aG1zIGxpa2Ugay1tZWFucyB0byB0cnkgYW5kIGlkZW50aWZ5IHNwZWNpZmljIHN1Ymdyb3Vwcy4NCg0KdC1TTkUgYW5kIFVNQVAsIG9uIHRoZSBvdGhlciBoYW5kIGFwcGVhciB0byBkbyBhIG11Y2ggYmV0dGVyIGpvYiB3aXRoIGhpZ2gtZGltZW5zaW9uYWwgZGF0YS4gVGhleSBjYW4gcHJlc2VydmUgbG9jYWwgc3RydWN0dXJlIGFuZCBVTUFQIGNhbiBhbHNvIGRvIGEgZmFpcmx5IGdvb2Qgam9iIG9mIHByZXNlcnZpbmcgZ2xvYmFsIHN0cnVjdHVyZS4gVGhlc2UgdG9vbHMgbWFrZSBmb3IgZ3JlYXQgZXhwbG9yYXRvcnkgYW5hbHlzaXMgb2YgeW91ciBjb21wbGV4IGRhdGFzZXRzLiBJbnRlcnByZXRhdGlvbiBvZiByZWxhdGlvbnNoaXBzLCBob3dldmVyLCBhcmUgbm90IG1hdGhlbWF0aWNhbGx5IGNsZWFyIGxpa2UgaW4gUENBLiBUaGVzZSBhcmUsIGFmdGVyIGFsbCBwcm9qZWN0aW9ucyBmcm9tIGEgaGlnaGVyIGRpbWVuc2lvbiBmb3Igb3VyIHNpbXBsZXIgcHJpbWF0ZSBicmFpbnMhDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyA1LjEuMCBXZWVrbHkgYXNzaWdubWVudA0KDQpUaGlzIHdlZWsncyBhc3NpZ25tZW50IHdpbGwgYmUgZm91bmQgdW5kZXIgdGhlIGN1cnJlbnQgbGVjdHVyZSBmb2xkZXIgdW5kZXIgdGhlICJhc3NpZ25tZW50IiBzdWJmb2xkZXIuIEl0IHdpbGwgaW5jbHVkZSBhbiBSIG1hcmtkb3duIG5vdGVib29rIHRoYXQgeW91IHdpbGwgdXNlIHRvIHByb2R1Y2UgdGhlIGNvZGUgYW5kIGFuc3dlcnMgZm9yIHRoaXMgd2VlaydzIGFzc2lnbm1lbnQuIFBsZWFzZSBwcm92aWRlIGFuc3dlcnMgaW4gbWFya2Rvd24gb3IgY29kZSBjZWxscyB0aGF0IGltbWVkaWF0ZWx5IGZvbGxvdyBlYWNoIHF1ZXN0aW9uIHNlY3Rpb24uDQoNCnwgICAgICAgICAgICAgICAgICAgIHwgQXNzaWdubWVudCBicmVha2Rvd24gfCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQp8Oi0tLS0tLS0tLS0tLS0tLS0tLTp8Oi0tLS0tLS0tLS0tLS0tLS0tLS0tOnw6LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tfA0KfCAgICAgICAgQ29kZSAgICAgICAgfCAgICAgICAgIDUwJSAgICAgICAgICB8IFwtIERvZXMgaXQgZm9sbG93IGJlc3QgcHJhY3RpY2VzPyAgICAgICAgICAgICAgIHwNCnwgICAgICAgICAgICAgICAgICAgIHwgICAgICAgICAgICAgICAgICAgICAgfCBcLSBEb2VzIGl0IG1ha2UgZ29vZCB1c2Ugb2YgYXZhaWxhYmxlIHBhY2thZ2VzPyB8DQp8ICAgICAgICAgICAgICAgICAgICB8ICAgICAgICAgICAgICAgICAgICAgIHwgXC0gV2FzIGRhdGEgcHJlcGFyZWQgcHJvcGVybHkgICAgICAgICAgICAgICAgICAgfA0KfCBBbnN3ZXJzIGFuZCBPdXRwdXQgfCAgICAgICAgIDUwJSAgICAgICAgICB8IFwtIElzIG91dHB1dCBiYXNlZCBvbiB0aGUgY29ycmVjdCBkYXRhc2V0PyAgICAgIHwNCnwgICAgICAgICAgICAgICAgICAgIHwgICAgICAgICAgICAgICAgICAgICAgfCBcLSBBcmUgZ3JvdXBpbmdzIGFwcHJvcHJpYXRlICAgICAgICAgICAgICAgICAgICB8DQp8ICAgICAgICAgICAgICAgICAgICB8ICAgICAgICAgICAgICAgICAgICAgIHwgXC0gQXJlIGNvcnJlY3QgdGl0bGVzL2F4ZXMvbGVnZW5kcyBjb3JyZWN0PyAgICAgfA0KfCAgICAgICAgICAgICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICB8IFwtIElzIGludGVycHJldGF0aW9uIG9mIHRoZSBncmFwaHMgY29ycmVjdD8gICAgIHwNCg0KU2luY2UgY29kaW5nIHN0eWxlcyBhbmQgc29sdXRpb25zIGNhbiBkaWZmZXIsIHN0dWRlbnRzIGFyZSBlbmNvdXJhZ2VkIHRvIHVzZSBiZXN0IHByYWN0aWNlcy4gQXNzaWdubWVudHMgKm1heSogYmUgcmV3YXJkZWQgZm9yIHdlbGwtY29kZWQgb3IgZWxlZ2FudCBzb2x1dGlvbnMuDQoNCllvdSBjYW4gc2F2ZSBhbmQgZG93bmxvYWQgdGhlIG1hcmtkb3duIG5vdGVib29rIGluIGl0cyBuYXRpdmUgZm9ybWF0LiBTdWJtaXQgdGhpcyBmaWxlIHRvIHRoZSB0aGUgYXBwcm9wcmlhdGUgYXNzaWdubWVudCBzZWN0aW9uIGJ5IDEyOjU5IHBtIG9uIHRoZSBkYXRlIG9mIG91ciBuZXh0IGNsYXNzOiBBcHJpbCAxOHRoLCAyMDI0Lg0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMgNS4yLjAgQWNrbm93bGVkZ2VtZW50cw0KDQoqKlJldmlzaW9uIDEuMC4wKio6IGNyZWF0ZWQgYW5kIHByZXBhcmVkIGZvciAqKkNTQjEwMjFIIFMgTEVDMDE0MSoqLCAwMy0yMDIxIGJ5IENhbHZpbiBNb2ssIFBoLkQuICpCaW9pbmZvcm1hdGljaWFuLCBFZHVjYXRpb24gYW5kIE91dHJlYWNoLCBDQUdFRi4qDQoNCioqUmV2aXNpb24gMS4wLjEqKjogZWRpdGVkIGFuZCBwcmVwYXJlZCBmb3IgKipDU0IxMDIwSCBTIExFQzAxNDEqKiwgMDMtMjAyMiBieSBDYWx2aW4gTW9rLCBQaC5ELiAqQmlvaW5mb3JtYXRpY2lhbiwgRWR1Y2F0aW9uIGFuZCBPdXRyZWFjaCwgQ0FHRUYuKg0KDQoqKlJldmlzaW9uIDEuMC4yKio6IGVkaXRlZCBhbmQgcHJlcGFyZWQgZm9yICoqQ1NCMTAyMEggUyBMRUMwMTQxKiosIDAzLTIwMjMgYnkgQ2FsdmluIE1vaywgUGguRC4gKkJpb2luZm9ybWF0aWNpYW4sIEVkdWNhdGlvbiBhbmQgT3V0cmVhY2gsIENBR0VGLioNCg0KKipSZXZpc2lvbiAyLjAuMCoqOiBSZXZpc2VkIGFuZCBwcmVwYXJlZCBmb3IgKipDU0IxMDIwSCBTIExFQzAxNDEqKiwgMDMtMjAyNCBieSBDYWx2aW4gTW9rLCBQaC5ELiAqQmlvaW5mb3JtYXRpY2lhbiwgRWR1Y2F0aW9uIGFuZCBPdXRyZWFjaCwgQ0FHRUYuKg0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMgNS4zLjAgUmVmZXJlbmNlcw0KDQpNb3JlIGluZm9ybWF0aW9uIG9uIGNhbGN1bGF0aW5nIG9wdGltYWwgY2x1c3RlcnM6IDxodHRwczovL3d3dy5kYXRhbm92aWEuY29tL2VuL2xlc3NvbnMvZGV0ZXJtaW5pbmctdGhlLW9wdGltYWwtbnVtYmVyLW9mLWNsdXN0ZXJzLTMtbXVzdC1rbm93LW1ldGhvZHMvPg0KDQpTdGVwLWJ5LXN0ZXAgaG93IFBDQSB3b3JrczogPGh0dHBzOi8vYnVpbHRpbi5jb20vZGF0YS1zY2llbmNlL3N0ZXAtc3RlcC1leHBsYW5hdGlvbi1wcmluY2lwYWwtY29tcG9uZW50LWFuYWx5c2lzPg0KDQpNb3JlIFBDQSBleHBsYW5hdGlvbnMgaGVyZTogPGh0dHBzOi8vc3RhdHMuc3RhY2tleGNoYW5nZS5jb20vcXVlc3Rpb25zLzI2OTEvbWFraW5nLXNlbnNlLW9mLXByaW5jaXBhbC1jb21wb25lbnQtYW5hbHlzaXMtZWlnZW52ZWN0b3JzLWVpZ2VudmFsdWVzPg0KDQpUaGUgbWF0aCBvZiBQQ0E6IDxodHRwczovL3d3dy5jcy5wcmluY2V0b24uZWR1L3BpY2Fzc28vbWF0cy9QQ0EtVHV0b3JpYWwtSW50dWl0aW9uX2pwLnBkZj4NCg0KdC1TTkUgaW4gUiBhbmQgUHl0aG9uOiA8aHR0cHM6Ly9kYXRhdml6cHlyLmNvbS9ob3ctdG8tbWFrZS10c25lLXBsb3QtaW4tci8+DQoNCkFsbCBhYm91dCBVTUFQOiA8aHR0cHM6Ly91bWFwLWxlYXJuLnJlYWR0aGVkb2NzLmlvL2VuL2xhdGVzdC9iYXNpY191c2FnZS5odG1sPg0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMgVGhlIENlbnRlciBmb3IgdGhlIEFuYWx5c2lzIG9mIEdlbm9tZSBFdm9sdXRpb24gYW5kIEZ1bmN0aW9uIChDQUdFRikNCg0KVGhlIENlbnRyZSBmb3IgdGhlIEFuYWx5c2lzIG9mIEdlbm9tZSBFdm9sdXRpb24gYW5kIEZ1bmN0aW9uIChDQUdFRikgYXQgdGhlIFVuaXZlcnNpdHkgb2YgVG9yb250byBvZmZlcnMgY29tcHJlaGVuc2l2ZSBleHBlcmltZW50YWwgZGVzaWduLCByZXNlYXJjaCwgYW5kIGFuYWx5c2lzIHNlcnZpY2VzIGluIG1pY3JvYmlvbWUgYW5kIG1ldGFnZW5vbWljIHN0dWRpZXMsIGdlbm9taWNzLCBwcm90ZW9taWNzLCBhbmQgYmlvaW5mb3JtYXRpY3MuDQoNCkZyb20gdGFyZ2V0ZWQgRE5BIGFtcGxpY29uIHNlcXVlbmNpbmcgdG8gdHJhbnNjcmlwdG9tZXMsIHdob2xlIGdlbm9tZXMsIGFuZCBtZXRhZ2Vub21lcywgZnJvbSBwcm90ZWluIGlkZW50aWZpY2F0aW9uIHRvIHBvc3QtdHJhbnNsYXRpb25hbCBtb2RpZmljYXRpb24sIENBR0VGIGhhcyB0aGUgdG9vbHMgYW5kIGtub3dsZWRnZSB0byBzdXBwb3J0IHlvdXIgcmVzZWFyY2guIE91ciBzdGF0ZS1vZi10aGUtYXJ0IGZhY2lsaXR5IGFuZCBleHBlcmllbmNlZCByZXNlYXJjaCBzdGFmZiBwcm92aWRlIGEgYnJvYWQgcmFuZ2Ugb2Ygc2VydmljZXMsIGluY2x1ZGluZyBib3RoIHN0YW5kYXJkIGFuYWx5c2VzIGFuZCB0ZWNobmlxdWVzIGRldmVsb3BlZCBieSBvdXIgdGVhbS4gSW4gcGFydGljdWxhciwgd2UgaGF2ZSBzcGVjaWFsIGV4cGVydGlzZSBpbiBtaWNyb2JpYWwsIHBsYW50LCBhbmQgZW52aXJvbm1lbnRhbCBzeXN0ZW1zLg0KDQpGb3IgbW9yZSBpbmZvcm1hdGlvbiBhYm91dCB1cyBhbmQgdGhlIHNlcnZpY2VzIHdlIG9mZmVyLCBwbGVhc2UgdmlzaXQgPGh0dHBzOi8vd3d3LmNhZ2VmLnV0b3JvbnRvLmNhLz4uDQoNCjo6OiB7YWxpZ249ImNlbnRlciJ9DQo8aW1nIHNyYz0iaHR0cHM6Ly9naXRodWIuY29tL2NhbW9rL0NTQl9Db3Vyc2VfTWF0ZXJpYWxzL2Jsb2IvbWFpbi9BZHZWaXovQ0FHRUZfbmV3LnBuZz9yYXc9dHJ1ZSIgd2lkdGg9IjcwMCIvPg0KOjo6DQo=